VCat聚合广告核心封装思路分享

哪个小可爱在偷偷的看我~~


偷瞄.gif

背景

在一个流量变现的年代,app如何收益逐渐提上日程,如何将app日活变现呢?广告是一个很好的途径
在曝光量有限的前提下,如何能将流量高质量的变现?请往下看,此篇文章提供广告核心封装思路,如何简单对接多方三方广告
本人在长时间广告开发过程中,相对对广告有了一定的认知,经过大日活app广告实线,沉淀出一套广告封装思想,以下将为大家分享一下核心功能封装
功能为简版,暂不包含各策略,如多方广告投放,单一广告失败其他广告补充,广告可控系统,串行请求,并行请求,超时暂停,竞价模式,启动图、霸屏、插屏预加载等等各策略
目的为,在有限的曝光位,展示最高价值广告,最优展示
当然,大家也可以选择简简单单对接一个三方平台,VCat是广告逐渐调优沉淀出来的,有不合理的地方可提出疑问,下面开始详细介绍功能实现

广告对外暴露调用方式

广告调用.jpg

如果需要,对外回掉事件也通过AdBuilder进行回掉

private fun getAd() {
        AdBuilder()
            .setAdLoadListener(
                object : SimpleAdLoadListener {
                    override fun onLoading(isLoading: Boolean) {
                    }

                    override fun onAdType(adCnType: AdVCatType) {
                    }

                    override fun onAdShow() {
                    }

                    override fun onNoAdOrDismissed() {
                    }
                },
            )
            .setAdId(binding.etAdid.text.toString().trim())
            .setController(binding.rootView)
            .setAdFrom(AdFrom.FROM_BAPING_AD)
            .build()
            .loadAdInfo()
    }


设计思路架构核心类
一、 对外暴露通过AdBuilder进行通信
二、 核心类AdLoadNotice,此类为贯穿所有策略的节点
三、 代理类AdRequestManager,代理核心类AdLoadNotice,负责分发
四、 AdForAdId广告解析adid向下分发
五、 TestAdId无需接口更改,本地自测adid
六、 VCatAdFactory工厂
七、 FactoryVCat生产者
八、 AdLoadVCat广告加载类
九、 AdRenderVCat广告渲染类
十、 AdStatistics统计类

简述类的由来

AdBuilder,获取外部参数,集中获取,build模式——>
AdLoadNotice,处理各种任务,线性串联,只负责串联,具体实现不用去管,可以优化成责任链模式——>
AdRequestManager,分担AdLoadNotice一部分任务,因为AdLoadNotice类分发内容会越来越多,需要个秘书进行代理,如需修改任何节点,秘书进行切换即可,代理模式——>
AdForAdId,此类只负责解析adid,新增、删减广告此类进行控制,此类角色为老板——>
TestAdId,此类为测试类,不跟随接口返回内容走,角色为老板开的后门——>
VCatAdFactory,此类为工厂,老板定好了需要生产啥,交与工厂进行生产,工厂模式——>
FactoryVCat,此类为工厂的部门,工厂可以生产多种物件,此类只负责生产某种产品,状态模式——>
AdLoadVCat,此类给工厂部门提供零件,专注于提供请求广告资源服务,策略模式——>
AdRenderVCat,此类给工厂部门提供零件,专注于提供请求广告渲染服务,策略模式——>
AdStatistics,此类给工厂部门提供零件,专注于提供请求广告统计服务,策略模式——>
注:所有任务做完,都会回掉给AdLoadNotice,让它进行再次分发,所以它是流水线的命脉,后续会增加 TimeOutEvent超时机制PolicyManager请求模式管理者PolicySingle单独一个广告请求PolicyBing并行广告请求,PolicyChuan串行广告请求,AdHandlerEvent下发节点控制类,竞价控制类等等,都是会通过AdLoadNotice进行切换

广告设计思想

1、我们想要的是,广告类型不管怎么增加,我们不关心流程的实现,只关心我新增,和结果,所以功能实现要分隔,新增只在AdForAdId中处理新增的adid进行向下分发,结果只在FactoryVCat工厂里进行新增的广告类型进行创建即可,新增的广告也不用关心其他的广告,不会有任何参杂,都是个体,没有任何关联
2、广告很多节点都是重复的,将功能抽象化,以抽象类的形式展现,调用也只调用抽象即可,不去关心子类,如IRenderUIILoadedIStatisticsOnAdFactory等,多肽的思想进行构建
3、广告展示分为2步骤,请求和渲染,互不影响,他们的任务没有任何联系,唯一的关联是请求的结果需要交给渲染类进行渲染,但是这一步也没有关联,请求的结果是交付给AdLoadNoticeAdLoadNotice代理给AdRequestManager,进行下发给渲染类,这样功能完全独立,如AdLoadCSJ只负责穿山甲的所有类型广告请求,如启动图,banner等等,请求都在我这里实现,而AdRenderCSJ只负责渲染,不关心任何功能
4、广告避免不了和外界的交互,比如外界需要展示的广告类型,此时都是通过AdBuilder进行通信,但是AdBuilder不能过于庞大,它的任务只是负责基础信息,如需要更多操作,需要如CreateAdBuilder 继承 AdBuilder,进行针对性扩展
5、为了和外界进行隔离,外界只给我们提供个容器即可,我们将自己的view添加到容器上,即实现了广告展示



广告流程图简版

流程图.jpg

项目结构如下

项目简单结构.jpg
关键核心类AdLoadNotice

后续增加各种策略,都是由核心类进行分发,核心类不需要具体实现,可以理解为此类为路由转折点

class AdLoadNotice internal constructor(private var builder: AdBuilder) : OnFactoryInfoListener {
   private var TAG = AD_TAG + builder.mAdFrom + "(" + builder.mAdFrom.adName + ") | "

   private var mAdRequestManager: AdRequestManager? = null

   init {
       mAdRequestManager = AdRequestManager(builder)
   }

   override fun getBuilder(): AdBuilder {
       return builder
   }


   /**
    * 加载广告 adInfo 未传,内部进行解析
    */
   override fun loadAdInfo(adInfo: VCatAdInfo?) {
       VCatLog.d(TAG + "广告发起请求")
       builder.setAdInfo(adInfo)

       builder.mOnAdLoad?.onLoading(true)

       loadAdWithType()
   }

   private fun loadAdWithType() {
       AdForAdId(builder, { adType: AdCnType, sdkEvent: SDKEvent ->
           builder.setAdCnType(adType)
           builder.setSDKEvent(sdkEvent)

           loadCnAd()
       }, {
           onNoAdNotice()
       })
   }

   /**
    * 广告发起 请求
    */
   private fun loadCnAd() {
       VCatLog.d(TAG + builder.mAdCnType.type + " | 广告发起 请求 ")

       VCatAdFactory(this, builder).create { loaded, renderUI, statistics ->

           builder.mOnAdLoad?.onAdType(builder.mAdCnType)

           mAdRequestManager?.loadAd(loaded, renderUI, statistics)
       }
   }

   override fun handlerEvent(it: Any?) {
       mAdRequestManager?.handlerEvent(it)
   }

   override fun handlerStatistics(it: Any?) {
       mAdRequestManager?.handlerStatistics(it)
   }

   /**
    * 无广告下发
    */
   override fun onNoAdNotice() {
       VCatLog.d(TAG + "下发无广告或广告关闭状态")
       builder.mOnAdLoad?.onLoading(false)
       builder.mOnAdLoad?.onNoAdOrDismissed()
       mAdRequestManager?.onDestroy()
   }

   fun onResume() {
       mAdRequestManager?.onResume()
   }

   fun onPause() {
       mAdRequestManager?.onPause()
   }

   fun onStop() {
       mAdRequestManager?.onStop()
   }

   fun onDestroy() {
       mAdRequestManager?.onDestroy()
   }

}

根据步骤进行拆解

一、广告整体思路梳理

1、 广告所需展示到某块区域,我们可提供个容器,将请求到的广告viewaddView到容器上,这样可以和页面进行隔离操作,只提供个容器即可
2、 广告所需参数,如context,广告所需展示的位置,ViewGroup容器,广告位所属的adid等,请求广告所需参数,AdBuilder负责整合所需参数,进行下传处理
3、 为了广告可控,一般我们会接口控制广告是否投放,所以我们需要个自己的接口,为了三方广告adid可变,即接口下发三方adid,所以此接口是必要的,我们简单称之为firstApi,即通过对应的广告位所属adid,通过接口请求三方的adid,用来请求三方
4、 广告分为2种,一种是三方广告,另一种是自研广告,可以投放一些自己公布的信息,自研广告不是必须的,我们按照有的思路去解析,我们可将自研广告也当作三方广告处理
5、 自研广告投放时机问题,常规思路,先请求firstApi如果下发的是自研广告,我们再请求自研Api,此时我们自己的广告系统会请求2次,才能获取到广告信息,所以不是最优方案,可以优化为请求一次,fistApi即下发自研广告资源,下发则优先渲染自研,无则下发三方adid(也可为集合,之后会作为并行,串行请求所需)
6、 下发广告后,包含adid等信息,此时我们不知道请求哪个三方,所以需要制定我们的规则,请求对应的三方广告,AdForAdId负责处理此逻辑
7、 广告创建,可以作为工厂模式进行处理,VCatAdFactory负责创建,策略模式处理创建哪一个
8、 单个广告举例,FactoryVCat某个策略,AdLoadVCat负责广告请求,AdRenderVCat负责广告渲染,AdStatistics负责广告统计

二、外部传递信息AdBuilder创建

AdBuilder获取外界传递各项参数,此类贯穿全局,获取值从此类中提取,外部获取状态也可以通过此类进行监听,如OnAdLoadListener
拓展:如果需要额外回掉,比如霸屏,创建单独的BuilderBaPingBuilder继承 AdBuilderBaPingBuilder独立增加额外的参数

open class AdBuilder {

    /**
     * 广告来源
     */
    internal lateinit var mAdFrom: AdFrom

    internal var mAdInfo: VCatAdInfo? = null

    internal var mViewSize: Pair<Int, Int> = Pair(640, 320)

    internal var mOnAdLoad: OnAdLoadListener? = null

    internal var mController: ViewGroup? = null

    internal var mContext: Context? = null

    internal var mAdId: String = ""

    internal lateinit var mAdCnType: AdCnType

    internal var mSDKEvent: SDKEvent? = null
    fun setAdId(adId: String): AdBuilder {
        mAdId = adId
        return this
    }

    fun setAdFrom(adFrom: AdFrom): AdBuilder {
        mAdFrom = adFrom
        return this
    }

    fun setAdLoadListener(onAdLoad: OnAdLoadListener?): AdBuilder {
        mOnAdLoad = onAdLoad
        return this
    }

    fun setViewSize(viewSize: Pair<Int, Int>): AdBuilder {
        mViewSize = viewSize
        return this
    }

    fun setController(controller: ViewGroup?): AdBuilder {
        mController = controller
        mContext = mController?.context
        return this
    }

    fun setAdInfo(adInfo: VCatAdInfo?) {
        mAdInfo = adInfo
    }

    fun setAdCnType(adCnType: AdCnType) {
        mAdCnType = adCnType
    }

    fun setSDKEvent(sdkEvent: SDKEvent?) {
        mSDKEvent = sdkEvent
    }

    fun build(): AdLoadNotice =
        if (::mAdFrom.isInitialized) {
            AdLoadNotice(this)
        } else {
            throw NullPointerException("adInfo or adFrom was null! 请检查是否设置!!!")
        }
}
二、关键核心类AdLoadNotice,分步骤创建AdLoadNotice

此类为贯穿所有策略的节点,AdLoadNotice持有AdBuilder,loadAdInfo()为解析入口,下一步需要解析adid

override fun loadAdInfo(adInfo: VCatAdInfo?) {
        VCatLog.d(TAG + "广告发起请求")
        builder.setAdInfo(adInfo)

        builder.mOnAdLoad?.onLoading(true)

        loadAdWithType()
    }

    private fun loadAdWithType() {
        AdForAdId(builder, { adType: AdCnType, sdkEvent: SDKEvent ->
            builder.setAdCnType(adType)
            builder.setSDKEvent(sdkEvent)

            loadCnAd()
        }, {
            onNoAdNotice()
        })
    }
三、adid解析类AdForAdId,规则需要自己定制,下面简单展示下实现内容
class AdForAdId(
    val builder: AdBuilder,
    private val callBack: (adType: AdCnType, sdkEvent: SDKEvent) -> Unit,
    private val onNoAdId: () -> Unit,
) {
    private var TAG = AD_TAG + builder.mAdFrom + "(" + builder.mAdFrom.adName + ") | "

    // 传入全部的 资源 VCat广告集合
    private val mVCatList: MutableList<SDKEvent> = mutableListOf()

    // 传入全部的 资源 穿山甲广告集合
    private val mCSJList: MutableList<SDKEvent> = mutableListOf()

    // 传入全部的 资源 广点通广告集合
    private val mGDTList: MutableList<SDKEvent> = mutableListOf()

    // 传入全部的 资源 百度广告集合
    private val mBAIDUList: MutableList<SDKEvent> = mutableListOf()

    init {
        initAdInfo()
    }

    private fun isReqConfig(): Boolean {
        return builder.mAdInfo == null
    }

    private fun initAdInfo() {
        with(mutableListOf<SDKEvent>()) {
            if (isReqConfig()) {
                add(SDKEvent())
            } else {
                builder.mAdInfo?.sdks?.let { addAll(it) }
            }

            if (BuildConfig.DEBUG) {
                TestAdId(builder) {
                    clear()
                    addAll(it)
                }
            }

            adTypeEvent(this)
        }
    }


    /**
     * 初始化广告信息
     */
    private fun adTypeEvent(sdks: List<SDKEvent>) {
        mVCatList.clear()
        mCSJList.clear()
        mGDTList.clear()

        if (isReqConfig()) {
            mVCatList.add(sdks.first())
        } else {
            sdks.forEach { sdkEvent ->
                if (sdkEvent.adid.isBlank()) {
                    return@forEach
                }
                when (sdkEvent.type) {
                    AdContent.csj -> {
                        mCSJList.add(sdkEvent)
                    }

                    AdContent.gdt -> {
                        mGDTList.add(sdkEvent)
                    }

                    AdContent.baidu -> {
                        mBAIDUList.add(sdkEvent)
                    }
                }
            }
        }

        adLogShow(sdks)

        loadAdWithType()
    }

    private fun loadAdWithType() {
        when {
            mVCatList.size > 0 -> {
                callBack.invoke(AdCnType.TYPE_VCAT, mVCatList.first())
            }

            mCSJList.size > 0 -> {
                callBack.invoke(AdCnType.TYPE_CSJ, mCSJList.first())
            }

            mGDTList.size > 0 -> {
                callBack.invoke(AdCnType.TYPE_GDT, mGDTList.first())
            }

            mBAIDUList.size > 0 -> {
                callBack.invoke(AdCnType.TYPE_BAIDU, mBAIDUList.first())
            }

            else -> {
                onNoAdId.invoke()
            }
        }
    }

    /**
     * 打印广告日志
     */
    private fun adLogShow(sdks: List<SDKEvent>) {
        if (isReqConfig()) {
            Timber.d(TAG + "配置接口请求:adid->" + builder.mAdId)
        } else {
            Timber.d(TAG + "打印所有集合:" + sdks)
            Timber.d(
                TAG + "广告SDK返回数据 集合所有 type:" +
                        sdks.stream().map(SDKEvent::type).collect(Collectors.toList())
            )
            Timber.d(
                TAG + "广告SDK返回数据 集合所有 adid:" +
                        sdks.stream().map(SDKEvent::adid).collect(Collectors.toList())
            )
        }
    }

}
四、广告开始发起请求,VCatAdFactory工厂模式
private fun loadCnAd() {
        VCatLog.d(TAG + builder.mAdCnType.type + " | 广告发起 请求 ")

        VCatAdFactory(this, builder).create { loaded, renderUI, statistics ->

            builder.mOnAdLoad?.onAdType(builder.mAdCnType)

            mAdRequestManager?.loadAd(loaded, renderUI, statistics)
        }
    }
class VCatAdFactory(val listener: OnFactoryInfoListener, val builder: AdBuilder) {

    fun create(callback: (loaded: Loaded, renderUI: RenderUI, statistics: Statistics?) -> Unit) {
        when (builder.mAdCnType) {

            AdCnType.TYPE_VCAT -> {
                FactoryVCat(listener, builder)
            }

            AdCnType.TYPE_CSJ -> {
                FactoryCSJ(listener, builder)
            }

            AdCnType.TYPE_GDT -> {
                FactoryGDT(listener, builder)
            }

            AdCnType.TYPE_BAIDU -> {
                FactoryBaiDu(listener, builder)
            }

        }.apply {
            callback.invoke(onCreateLoader(), onCreateRender(), onCreateStatistics())
        }
    }
}
五、策略模式创建各种广告,简单以穿山甲为例
interface OnAdFactory {
    fun onCreateLoader(): Loaded
    fun onCreateRender(): RenderUI
    fun onCreateStatistics(): Statistics?
}
class FactoryCSJ(val listener: OnFactoryInfoListener, val builder: AdBuilder) : OnAdFactory {

    override fun onCreateLoader(): Loaded {
        return AdLoadCSJ(builder, {
            it?.let {
                listener.handlerEvent(it)
            } ?: run {
                listener.onNoAdNotice()
            }
        }, {
            listener.handlerStatistics(it)
        })
    }

    override fun onCreateRender(): RenderUI {
        return AdRenderCSJ()
    }

    override fun onCreateStatistics(): Statistics {
        return AdStatistics(builder)
    }

}
六、简述穿山甲请求类AdLoadCSJ,抽象接口Loaded
interface Loaded {
    fun load(builder: AdBuilder) {

        loadLogTag(AD_TAG + builder.mAdFrom + "(" + builder.mAdFrom.adName + ") | ")

        when (builder.mAdFrom) {
            AdFrom.FROM_QDY_AD -> {
                loadQiDongYe()
            }
            AdFrom.FROM_BANNER_AD -> {
                loadBanner()
            }
            AdFrom.FROM_BAPING_AD -> {
                loadBaPing()
            }
            AdFrom.FROM_CHAPING_AD -> {
                loadChaPing()
            }
            AdFrom.FROM_QTP_AD -> {
                loadTiePian()
            }
            AdFrom.FROM_FEED_BANNER_AD -> {
                loadFeed()
            }
            AdFrom.FROM_LUNBO_AD -> {
                loadLunBo()
            }
            AdFrom.FROM_PAUSE_AD -> {
                loadPause()
            }
            else -> {
            }
        }
    }

    fun loadLogTag(tag: String)

    fun loadQiDongYe()

    fun loadBanner()

    fun loadBaPing()

    fun loadChaPing()

    fun loadTiePian()

    fun loadFeed()

    fun loadLunBo()

    fun loadPause()
}

class AdLoadCSJ(
    private val builder: AdBuilder,
    private val callBack: (listener: Any?) -> Unit,
    private val onAdStatistics: (listener: Any?) -> Unit,
) : Loaded {
    private var TAG = ""
    override fun loadLogTag(tag: String) {
        TAG = tag + "穿山甲广告 | "

        VCatLog.d(TAG + "adId:" + builder.mSDKEvent?.adid)
    }

    override fun loadQiDongYe() {
        callBack.invoke(null)
    }

    override fun loadBanner() {
        callBack.invoke(null)
    }

    override fun loadBaPing() {
        callBack.invoke(null)
    }

    override fun loadChaPing() {
        callBack.invoke(null)
    }

    override fun loadTiePian() {
        callBack.invoke(null)
    }

    override fun loadFeed() {
        callBack.invoke(null)
    }

    override fun loadLunBo() {
        callBack.invoke(null)
    }

    override fun loadPause() {
        callBack.invoke(null)
    }
}

七、AdLoadNotice继续进行分发,分发通过代理类进行分发AdRequestManager
class AdRequestManager(private var builder: AdBuilder) {
   private var mRenderUI: RenderUI? = null
   private var mStatistics: Statistics? = null

   fun loadAd(
       loaded: Loaded,
       renderUI: RenderUI,
       statistics: Statistics? = null,
   ) {
       mRenderUI = renderUI
       mStatistics = statistics

       statistics?.statistics(builder)
       loaded.load(builder)
   }


   fun onResume() {
       mRenderUI?.onResume()
   }

   fun onPause() {
       mRenderUI?.onPause()
   }

   fun onStop() {
       mRenderUI?.onStop()
   }

   fun onDestroy() {
       mRenderUI?.onDestroy()
   }

   fun handlerEvent(it: Any?) {
       mRenderUI?.handlerEvent(it, builder)
   }

   fun handlerStatistics(it: Any?) {
       mStatistics?.handlerEvent(it)
   }
}
八、广告渲染类负责渲染AdRenderCSJ,抽象接口RenderUI
interface RenderUI {
    fun initData(builder: AdBuilder)

    fun handlerEvent(
        it: Any?,
        builder: AdBuilder,
    ) {
        initData(builder)
    }

    fun onResume()

    fun onPause()

    fun onStop()

    fun onDestroy()
}
class AdRenderCSJ : RenderUI {
    private var TAG = AD_TAG

    private var mAdFrom: AdFrom? = null
    private var mBuilder: AdBuilder? = null

    override fun initData(builder: AdBuilder) {
        mBuilder = builder
        mAdFrom = builder.mAdFrom

        TAG = AD_TAG + mAdFrom + "(" + mAdFrom?.adName + ") | "

        mBuilder?.mOnAdLoad?.onAdShow()
        mBuilder?.mOnAdLoad?.onLoading(false)
    }

    override fun handlerEvent(it: Any?, builder: AdBuilder) {
        TODO("Not yet implemented")
    }

    override fun onResume() {
        TODO("Not yet implemented")
    }

    override fun onPause() {
        TODO("Not yet implemented")
    }

    override fun onStop() {
        TODO("Not yet implemented")
    }

    override fun onDestroy() {
        TODO("Not yet implemented")
    }
}
八、核心类AdLoadNotice案例代码,后续增加各种策略,核心类进行切换,核心类只负责分发
class AdLoadNotice internal constructor(private var builder: AdBuilder) : OnFactoryInfoListener {
   private var TAG = AD_TAG + builder.mAdFrom + "(" + builder.mAdFrom.adName + ") | "

   private var mAdRequestManager: AdRequestManager? = null

   init {
       mAdRequestManager = AdRequestManager(builder)
   }

   override fun getBuilder(): AdBuilder {
       return builder
   }


   /**
    * 加载广告 adInfo 未传,内部进行解析
    */
   override fun loadAdInfo(adInfo: VCatAdInfo?) {
       VCatLog.d(TAG + "广告发起请求")
       builder.setAdInfo(adInfo)

       builder.mOnAdLoad?.onLoading(true)

       loadAdWithType()
   }

   private fun loadAdWithType() {
       AdForAdId(builder, { adType: AdCnType, sdkEvent: SDKEvent ->
           builder.setAdCnType(adType)
           builder.setSDKEvent(sdkEvent)

           loadCnAd()
       }, {
           onNoAdNotice()
       })
   }

   /**
    * 广告发起 请求
    */
   private fun loadCnAd() {
       VCatLog.d(TAG + builder.mAdCnType.type + " | 广告发起 请求 ")

       VCatAdFactory(this, builder).create { loaded, renderUI, statistics ->

           builder.mOnAdLoad?.onAdType(builder.mAdCnType)

           mAdRequestManager?.loadAd(loaded, renderUI, statistics)
       }
   }

   override fun handlerEvent(it: Any?) {
       mAdRequestManager?.handlerEvent(it)
   }

   override fun handlerStatistics(it: Any?) {
       mAdRequestManager?.handlerStatistics(it)
   }

   /**
    * 无广告下发
    */
   override fun onNoAdNotice() {
       VCatLog.d(TAG + "下发无广告或广告关闭状态")
       builder.mOnAdLoad?.onLoading(false)
       builder.mOnAdLoad?.onNoAdOrDismissed()
       mAdRequestManager?.onDestroy()
   }

   fun onResume() {
       mAdRequestManager?.onResume()
   }

   fun onPause() {
       mAdRequestManager?.onPause()
   }

   fun onStop() {
       mAdRequestManager?.onStop()
   }

   fun onDestroy() {
       mAdRequestManager?.onDestroy()
   }

}


注:广告迭代功能会越来越复杂,分层合理后续迭代会简单很多



此篇文章为简述版,如需广告开发,可汲取其中设计思路进行拓展,
——
功能为简版,暂不包含各策略,如多方广告投放,单一广告失败其他广告补充,广告可控系统,串行请求,并行请求,超时暂停,竞价模式,启动图、霸屏、插屏预加载等等各策略
——

最后祝大家开发愉快

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,319评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,801评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,567评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,156评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,019评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,090评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,500评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,192评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,474评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,566评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,338评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,212评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,572评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,890评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,169评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,478评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,661评论 2 335

推荐阅读更多精彩内容