無(wú)痕埋點(diǎn)方案探究

目前埋點(diǎn)的設(shè)計(jì)大致有以下幾種:
參考 網(wǎng)易HubbleData無(wú)埋點(diǎn)SDK在iOS端的設(shè)計(jì)與實(shí)現(xiàn)

1铅协、代碼埋點(diǎn)
由開(kāi)發(fā)人員在觸發(fā)事件的具體方法里胸懈,植入多行代碼把需要上傳的參數(shù)上報(bào)至服務(wù)端更卒。

2、可視化埋點(diǎn)
根據(jù)標(biāo)識(shí)來(lái)識(shí)別每一個(gè)事件吨娜, 針對(duì)指定的事件進(jìn)行取參埋點(diǎn)问拘。而事件的標(biāo)識(shí)與參數(shù)信息都寫(xiě)在配置表中,通過(guò)動(dòng)態(tài)下發(fā)配置表來(lái)實(shí)現(xiàn)埋點(diǎn)統(tǒng)計(jì)压鉴。

3崖咨、無(wú)埋點(diǎn)
無(wú)埋點(diǎn)并不是不需要埋點(diǎn),更準(zhǔn)確的說(shuō)應(yīng)該是“全埋”油吭, 前端的任意一個(gè)事件都被綁定一個(gè)標(biāo)識(shí)击蹲,所有的事件都別記錄下來(lái)。 通過(guò)定期上傳記錄文件婉宰,配合文件解析歌豺,解析出來(lái)我們想要的數(shù)據(jù), 并生成可視化報(bào)告供專(zhuān)業(yè)人員分析 心包, 因此實(shí)現(xiàn)“無(wú)埋點(diǎn)”統(tǒng)計(jì)类咧。

可視化埋點(diǎn)

首先,可視化埋點(diǎn)并非完全拋棄了代碼埋點(diǎn)蟹腾,而是在代碼埋點(diǎn)的上層封裝的一套邏輯來(lái)代替手工埋點(diǎn)痕惋,大體上架構(gòu)如下圖:


3104472-15d0364de7f22ecd.png

不過(guò)要實(shí)現(xiàn)可視化埋點(diǎn)也有很多問(wèn)題需要解決,比如事件唯一標(biāo)識(shí)的確定娃殖,業(yè)務(wù)參數(shù)的獲取值戳,有邏輯判斷的埋點(diǎn)配置項(xiàng)信息等等。接下來(lái)我會(huì)重點(diǎn)圍繞唯一標(biāo)識(shí)以及業(yè)務(wù)參數(shù)獲取這兩個(gè)問(wèn)題給出自己的一個(gè)解決方案炉爆。

唯一標(biāo)識(shí)問(wèn)題

唯一標(biāo)識(shí)的組成方式主要是又 target + action 來(lái)確定堕虹, 即任何一個(gè)事件都存在一個(gè)target與action。 在此引入AOP編程芬首,AOP(Aspect-Oriented-Programming)即面向切面編程的思想鲫凶,基于 Runtime 的 Method Swizzling能力,來(lái) hook 相應(yīng)的方法衩辟,從而在hook方法中進(jìn)行統(tǒng)一的埋點(diǎn)處理螟炫。例如所有的按鈕被點(diǎn)擊時(shí),都會(huì)觸發(fā)UIApplication的sendAction方法艺晴,我們hook這個(gè)方法昼钻,即可攔截所有按鈕的點(diǎn)擊事件。


3104472-3b1942be410c1e02.jpeg

這里主要分為兩個(gè)部分 :

  • 事件的鎖定
    事件的鎖定主要是靠 “事件唯一標(biāo)識(shí)符”來(lái)鎖定封寞,而事件的唯一標(biāo)識(shí)是由我們寫(xiě)入配置表中的然评。

  • 埋點(diǎn)數(shù)據(jù)的上報(bào)。
    埋點(diǎn)數(shù)據(jù)的數(shù)據(jù)又分為兩種類(lèi)型: 固定數(shù)據(jù)與可變的業(yè)務(wù)數(shù)據(jù)狈究, 而固定數(shù)據(jù)我們可以直接寫(xiě)到配置表中碗淌, 通過(guò)唯一標(biāo)識(shí)來(lái)獲取。而對(duì)于業(yè)務(wù)數(shù)據(jù),我是這么理解的: 數(shù)據(jù)是有持有者的亿眠, 例如我們Controller的一個(gè)屬性值碎罚, 又或者數(shù)據(jù)再M(fèi)odel的某一個(gè)層級(jí)。 這么的話我們就可以通過(guò)KVC的的方式來(lái)遞歸獲取該屬性的值來(lái)取到業(yè)務(wù)數(shù)據(jù)纳像, 代碼后面會(huì)有介紹荆烈。

整體代碼示例

由于iOS中的事件場(chǎng)景是多樣的, 在此我以UIControl, UITablview(collectionView與tableView基本相同)竟趾, UITapGesture憔购, UIViewController的PV統(tǒng)計(jì) 為例,介紹一下具體思路岔帽。

1玫鸟、UIViewController PV統(tǒng)計(jì)

頁(yè)面的統(tǒng)計(jì)較為簡(jiǎn)單,利用Method Swizzing hook 系統(tǒng)的viewDidLoad犀勒, 直接通過(guò)頁(yè)面名稱(chēng)即可鎖定頁(yè)面的展示代碼如下:

@implementation UIViewController (Analysis)

+(void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{

        SEL originalDidLoadSelector = @selector(viewDidLoad);
        SEL swizzingDidLoadSelector = @selector(user_viewDidLoad);
        [MethodSwizzingTool swizzingForClass:[self class] originalSel:originalDidLoadSelector swizzingSel:swizzingDidLoadSelector];

    });
}

-(void)user_viewDidLoad
{
    [self user_viewDidLoad];

   //從配置表中取參數(shù)的過(guò)程 1 固定參數(shù)  2 業(yè)務(wù)參數(shù)(此處參數(shù)被target持有)
    NSString * identifier = [NSString stringWithFormat:@"%@", [self class]];
    NSDictionary * dic = [[[DataContainer dataInstance].data objectForKey:@"PAGEPV"] objectForKey:identifier];
    if (dic) {
        NSString * pageid = dic[@"userDefined"][@"pageid"];
        NSString * pagename = dic[@"userDefined"][@"pagename"];
        NSDictionary * pagePara = dic[@"pagePara"];

        __block NSMutableDictionary * uploadDic = [NSMutableDictionary dictionaryWithCapacity:0];
        [pagePara enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {

            id value = [CaptureTool captureVarforInstance:self withPara:obj];
            if (value && key) {
                [uploadDic setObject:value forKey:key];
            }
        }];

        NSLog(@"\n 事件唯一標(biāo)識(shí)為:%@ \n  pageid === %@,\n  pagename === %@,\n pagepara === %@ \n", [self class], pageid, pagename, uploadDic);
    }
}

2屎飘、UIControl 點(diǎn)擊統(tǒng)計(jì)

主要通過(guò)hook sendAction:to:forEvent: 來(lái)實(shí)現(xiàn), 其唯一標(biāo)識(shí)符我們用 targetname/selector/tag來(lái)標(biāo)記,具體代碼如下:

~~~ 
@implementation UIControl (Analysis)
+(void)load 
{ 
static dispatch_once_t onceToken; 
dispatch_once(&onceToken, ^{ 
SEL originalSelector = @selector(sendAction:to:forEvent:); 
SEL swizzingSelector = @selector(user_sendAction:to:forEvent:); 
[MethodSwizzingTool swizzingForClass:[self class] originalSel:originalSelector swizzingSel:swizzingSelector]; 
}); 
}

-(void)user_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event 
{ 
[self user_sendAction:action to:target forEvent:event];

NSString * identifier = [NSString stringWithFormat:@"%@/%@/%ld", [target class], NSStringFromSelector(action),self.tag];
NSDictionary * dic = [[[DataContainer dataInstance].data objectForKey:@"ACTION"] objectForKey:identifier];
if (dic) {

    NSString * eventid = dic[@"userDefined"][@"eventid"];
    NSString * targetname = dic[@"userDefined"][@"target"];
    NSString * pageid = dic[@"userDefined"][@"pageid"];
    NSString * pagename = dic[@"userDefined"][@"pagename"];
    NSDictionary * pagePara = dic[@"pagePara"];
    __block NSMutableDictionary * uploadDic = [NSMutableDictionary dictionaryWithCapacity:0];
    [pagePara enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {

        id value = [CaptureTool captureVarforInstance:target withPara:obj];
        if (value && key) {
            [uploadDic setObject:value forKey:key];
        }
    }];


    NSLog(@" \n  唯一標(biāo)識(shí)符為 : %@, \n event id === %@,\n  target === %@, \n  pageid === %@,\n  pagename === %@,\n pagepara === %@ \n", identifier, eventid, targetname, pageid, pagename, uploadDic);
}
} 
~~~

3账蓉、TableView (CollectionView) 的點(diǎn)擊統(tǒng)計(jì)

tablview的唯一標(biāo)識(shí)枚碗, 我們使用 delegate.class/tableview.class/tableview.tag的組合來(lái)唯一鎖定。 主要是通過(guò)hook setDelegate 方法铸本, 在設(shè)置代理的時(shí)候再去交互 didSelect 方法來(lái)實(shí)現(xiàn)肮雨, 具體的原理是 具體代碼如下:

@implementation UITableView (Analysis)

+(void)load 
{ 
static dispatch_once_t onceToken; 
dispatch_once(&onceToken, ^{

    SEL originalAppearSelector = @selector(setDelegate:);
    SEL swizzingAppearSelector = @selector(user_setDelegate:);
    [MethodSwizzingTool swizzingForClass:[self class] originalSel:originalAppearSelector swizzingSel:swizzingAppearSelector];
});
}

-(void)user_setDelegate:(id)delegate 
{ 
[self user_setDelegate:delegate];

SEL sel = @selector(tableView:didSelectRowAtIndexPath:);
1
// 初始化一個(gè)名字為 delegate.class/tableview.class/tableview.tag 的selector 
SEL sel_ = NSSelectorFromString([NSString stringWithFormat:@”%@/%ld”, [self class], self.tag]);

// 將生成的selector的方法 加入的 delegate類(lèi)中, 并且該方法的實(shí)現(xiàn)(IMP)指向當(dāng)前類(lèi)user_tableView:didSelectRowAtIndexPath: 方法的實(shí)現(xiàn) 
class_addMethod([delegate class], 
sel_, 
method_getImplementation(class_getInstanceMethod([self class], @selector(user_tableView:didSelectRowAtIndexPath:))), 
nil);

//判斷是否有實(shí)現(xiàn)箱玷,沒(méi)有的話添加一個(gè)實(shí)現(xiàn)
if (![self isContainSel:sel inClass:[delegate class]]) {
    IMP imp = method_getImplementation(class_getInstanceMethod([delegate class], sel));
    class_addMethod([delegate class], sel, imp, nil);
}


// 將swizzle delegate method 和 origin delegate method 交換
[MethodSwizzingTool swizzingForClass:[delegate class] originalSel:sel swizzingSel:sel_];
}

//判斷頁(yè)面是否實(shí)現(xiàn)了某個(gè)sel 
- (BOOL)isContainSel:(SEL)sel inClass:(Class)class { 
unsigned int count;

Method *methodList = class_copyMethodList(class,&count);
for (int i = 0; i < count; i++) {
    Method method = methodList[i];
    NSString *tempMethodString = [NSString stringWithUTF8String:sel_getName(method_getName(method))];
    if ([tempMethodString isEqualToString:NSStringFromSelector(sel)]) {
        return YES;
    }
}
return NO;
}

// 由于我們交換了方法怨规, 所以在tableview的 didselected 被調(diào)用的時(shí)候, 實(shí)質(zhì)調(diào)用的是以下方法:
-(void)user_tableView:(UITableView )tableView didSelectRowAtIndexPath:(NSIndexPath )indexPath 
{

//通過(guò)唯一標(biāo)識(shí)的規(guī)則锡足, 找到原來(lái)的方法 (即tableView:didSelectRowAtIndexPath: 方法) 
SEL sel = NSSelectorFromString([NSString stringWithFormat:@”%@/%ld”, [tableView class], tableView.tag]); 
if ([self respondsToSelector:sel]) { 
//以下是對(duì)方法的調(diào)用以及傳參波丰,performSelector 方法底層實(shí)現(xiàn)與此相似 
IMP imp = [self methodForSelector:sel]; 
void (func)(id, SEL,id,id) = (void )imp; 
func(self, sel,tableView,indexPath); 
}

//配置表中, 事件唯一標(biāo)識(shí)即為key舶得, 通過(guò)key 取value掰烟, 取到了就說(shuō)明該事件配置的有埋點(diǎn)上傳
NSString * identifier = [NSString stringWithFormat:@"%@/%@/%ld", [self class],[tableView class], tableView.tag];
NSDictionary * dic = [[[DataContainer dataInstance].data objectForKey:@"TABLEVIEW"] objectForKey:identifier];
if (dic) {

    NSString * eventid = dic[@"userDefined"][@"eventid"];
    NSString * targetname = dic[@"userDefined"][@"target"];
    NSString * pageid = dic[@"userDefined"][@"pageid"];
    NSString * pagename = dic[@"userDefined"][@"pagename"];
    NSDictionary * pagePara = dic[@"pagePara"];

    UITableViewCell * cell = [tableView cellForRowAtIndexPath:indexPath];
    __block NSMutableDictionary * uploadDic = [NSMutableDictionary dictionaryWithCapacity:0];
    [pagePara enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {
        NSInteger containIn = [obj[@"containIn"] integerValue];
        //通過(guò)containIn 參數(shù)判斷數(shù)據(jù)持有者,后續(xù)會(huì)有解釋
        id instance = containIn == 0 ? self : cell;
        id value = [CaptureTool captureVarforInstance:instance withPara:obj];
        if (value && key) {
            [uploadDic setObject:value forKey:key];
        }
    }];

    NSLog(@"\n 事件的唯一標(biāo)識(shí)為 %@沐批, \n event id === %@,\n  target === %@, \n  pageid === %@,\n  pagename === %@,\n pagepara === %@ \n", identifier,  eventid, targetname, pageid, pagename, uploadDic);
}
}

4纫骑、gesture方式添加的的點(diǎn)擊統(tǒng)計(jì)

gesture的事件,是通過(guò) hook initWithTarget:action:方法來(lái)實(shí)現(xiàn)的九孩, 事件的唯一標(biāo)識(shí)依然是target.class/actionname來(lái)鎖定的先馆, 代碼如下:

@implementation UIGestureRecognizer (Analysis)

(void)load 
{ 
static dispatch_once_t onceToken; 
dispatch_once(&onceToken, ^{

[MethodSwizzingTool swizzingForClass:[self class] originalSel:@selector(initWithTarget:action:) swizzingSel:@selector(vi_initWithTarget:action:)];
1
}); 
}

(instancetype)vi_initWithTarget:(nullable id)target action:(nullable SEL)action 
{ 
UIGestureRecognizer *selfGestureRecognizer = [self vi_initWithTarget:target action:action];

if (!target && !action) { 
return selfGestureRecognizer; 
}

if ([target isKindOfClass:[UIScrollView class]]) { 
return selfGestureRecognizer; 
}

Class class = [target class];

SEL originalSEL = action;

NSString * sel_name = [NSString stringWithFormat:@”%s/%@”, class_getName([target class]),NSStringFromSelector(action)]; 
SEL swizzledSEL = NSSelectorFromString(sel_name);

BOOL isAddMethod = class_addMethod(class, 
swizzledSEL, 
method_getImplementation(class_getInstanceMethod([self class], @selector(responseUser_gesture:))), 
nil);

if (isAddMethod) { 
[MethodSwizzingTool swizzingForClass:class originalSel:originalSEL swizzingSel:swizzledSEL]; 
}

self.name = NSStringFromSelector(action); 
return selfGestureRecognizer; 
}

-(void)responseUser_gesture:(UIGestureRecognizer *)gesture 
{

NSString * identifier = [NSString stringWithFormat:@"%s/%@", class_getName([self class]),gesture.name];

SEL sel = NSSelectorFromString(identifier);
if ([self respondsToSelector:sel]) {
    IMP imp = [self methodForSelector:sel];
    void (*func)(id, SEL,id) = (void *)imp;
    func(self, sel,gesture);
}


NSDictionary * dic = [[[DataContainer dataInstance].data objectForKey:@"GESTURE"] objectForKey:identifier];
if (dic) {

    NSString * eventid = dic[@"userDefined"][@"eventid"];
    NSString * targetname = dic[@"userDefined"][@"target"];
    NSString * pageid = dic[@"userDefined"][@"pageid"];
    NSString * pagename = dic[@"userDefined"][@"pagename"];
    NSDictionary * pagePara = dic[@"pagePara"];

    __block NSMutableDictionary * uploadDic = [NSMutableDictionary dictionaryWithCapacity:0];
    [pagePara enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {
        id value = [CaptureTool captureVarforInstance:self withPara:obj];
        if (value && key) {
            [uploadDic setObject:value forKey:key];
        }
    }];

    NSLog(@"\n事件的唯一標(biāo)識(shí)為 %@, \n event id === %@,\n  target === %@, \n  pageid === %@,\n  pagename === %@,\n pagepara === %@ \n", identifier躺彬, eventid, targetname, pageid, pagename, uploadDic);

}
} 
@end 

配置表結(jié)構(gòu)

首先那煤墙, 配置表是一個(gè)json數(shù)據(jù)梅惯。 針對(duì)不同的場(chǎng)景 (UIControl , 頁(yè)面PV, Tabeview, Gesture)都做了區(qū)分仿野, 用不同的key區(qū)別铣减。 對(duì)于 “固定參數(shù)” , 我們之間寫(xiě)到配置表中设预,而對(duì)于業(yè)務(wù)參數(shù)徙歼, 我們之間寫(xiě)清楚參數(shù)在業(yè)務(wù)內(nèi)的名字犁河, 以及上傳時(shí)的 keyName鳖枕, 參數(shù)的持有者。 通過(guò)Runtime + KVC來(lái)取值桨螺。 配置表可以是這個(gè)樣子:(僅供參考)

{
    "ACTION": {
        "ViewController/jumpSecond": {
            "userDefined": {
                "eventid": "201803074|93",
                "target": "",
                "pageid": "234",
                "pagename": "button點(diǎn)擊宾符,跳轉(zhuǎn)至下一個(gè)頁(yè)面"
            },
            "pagePara": {
                "testKey9": {
                    "propertyName": "testPara",
                    "propertyPath":"",
                    "containIn": "0"
                }
            }
        },
        
        "SecondViewController/back": {
            "userDefined": {
                "eventid": "201803074|965",
                "target": "second",
                "pageid": "234",
                "pagename": "button點(diǎn)擊,返回"
            },
            "pagePara": {
                "testKey9": {
                    "propertyName": "testPara",
                    "propertyPath":"",
                    "containIn": "0"
                }
            }
        }
    },
    
    "PAGEPV": {
        "ViewController": {
            "userDefined": {
                "pageid": "234",
                "pagename": "XXX 頁(yè)面展示了"
            },
            "pagePara": {
                "testKey10": {
                    "propertyName": "testPara",
                    "propertyPath":"",
                    "containIn": "0"
                }
            }
        },
        "SecondViewController": {
            "userDefined": {
                "pageid": "234",
                "pagename": "XXX頁(yè)面展示"
            },
            "pagePara": {
                "testKey0": {
                    "propertyName": "age",
                    "propertyPath":"",
                    "containIn": "0"
                }
            }
        }
    },
    "TABLEVIEW": {
        "ViewController/TestTableview/0":{
            "userDefined": {
                "eventid": "201803074|93",
                "target": "",
                "pageid": "234",
                "pagename": "tableview 被點(diǎn)擊"
            },
            "pagePara": {
                "user_grade": {
                    "propertyName": "grade",
                    "propertyPath":"",
                    "containIn": "1"
                }
            }
        }
    },
    
    "GESTURE": {
        "ViewController/gesture1clicked:":{
            "userDefined": {
                "eventid": "201803074|93",
                "target": "",
                "pageid": "手勢(shì)1對(duì)應(yīng)的id",
                "pagename": "手勢(shì)1對(duì)應(yīng)的page name"
            },
            "pagePara": {
                "testKey1": {
                    "propertyName": "testPara",
                    "propertyPath":"",
                    "containIn": "0"
                }
                
            }
        },
        "ViewController/gesture2clicked:":{
            "userDefined": {
                "eventid": "201803074|93",
                "target": "",
                "pageid": "手勢(shì)2對(duì)應(yīng)的id",
                "pagename": "手勢(shì)2對(duì)應(yīng)的page name"
            },
            "pagePara": {
                "testKey2": {
                    "propertyName": "testPara",
                    "propertyPath":"",
                    "containIn": "0"
                }
                
            }
        },
        
        "SecondViewController/gesture3clicked:":{
            "userDefined": {
                "eventid": "201803074|98",
                "target": "",
                "pageid": "gesture3clicked",
                "pagename": "手勢(shì)3對(duì)應(yīng)的page name"
            },
            "pagePara": {
                "user_age": {
                    "propertyName": "goodsnumber",
                    "propertyPath":"",
                }
                
            }
        }
    }
}

取參方法


#import "CaptureTool.h"
#import <objc/runtime.h>

@implementation CaptureTool

+(id)captureVarforInstance:(id)instance varName:(NSString *)varName
{
    id value = [instance valueForKey:varName];

    unsigned int count;
    objc_property_t *properties = class_copyPropertyList([instance class], &count);
    
    if (!value) {
        NSMutableArray * varNameArray = [NSMutableArray arrayWithCapacity:0];
        for (int i = 0; i < count; i++) {
            objc_property_t property = properties[i];
            NSString* propertyAttributes = [NSString stringWithUTF8String:property_getAttributes(property)];
            NSArray* splitPropertyAttributes = [propertyAttributes componentsSeparatedByString:@"\""];
            if (splitPropertyAttributes.count < 2) {
                continue;
            }
            NSString * className = [splitPropertyAttributes objectAtIndex:1];
            Class cls = NSClassFromString(className);
            NSBundle *bundle2 = [NSBundle bundleForClass:cls];
            if (bundle2 == [NSBundle mainBundle]) {
//                NSLog(@"自定義的類(lèi)----- %@", className);
                const char * name = property_getName(property);
                NSString * varname = [[NSString alloc] initWithCString:name encoding:NSUTF8StringEncoding];
                [varNameArray addObject:varname];
            } else {
//                NSLog(@"系統(tǒng)的類(lèi)");
            }
        }
        
        for (NSString * name in varNameArray) {
            id newValue = [instance valueForKey:name];
            if (newValue) {
                value = [newValue valueForKey:varName];
                if (value) {
                    return value;
                }else{
                    value = [[self class] captureVarforInstance:newValue varName:varName];
                }
            }
        }
    }
    return value;
}


+(id)captureVarforInstance:(id)instance withPara:(NSDictionary *)para
{
    NSString * properyName = para[@"propertyName"];
    // 實(shí)例中包含其他對(duì)象的情況
    NSString * propertyPath = para[@"propertyPath"];
    if (propertyPath.length > 0) {
        NSArray * keysArray = [propertyPath componentsSeparatedByString:@"/"];
     
        return [[self class] captureVarforInstance:instance withKeys:keysArray];
    }
    return [[self class] captureVarforInstance:instance varName:properyName];
}

+(id)captureVarforInstance:(id)instance withKeys:(NSArray *)keyArray
{
    id result = [instance valueForKey:keyArray[0]];
    
    if (keyArray.count > 1 && result) {
        int i = 1;
        while (i < keyArray.count && result) {
            result = [result valueForKey:keyArray[i]];
            i++;
        }
    }
    return result;
}

@end

——–項(xiàng)目github地址

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末灭翔,一起剝皮案震驚了整個(gè)濱河市魏烫,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌肝箱,老刑警劉巖哄褒,帶你破解...
    沈念sama閱讀 212,718評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異煌张,居然都是意外死亡呐赡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)骏融,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)链嘀,“玉大人,你說(shuō)我怎么就攤上這事档玻』巢矗” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 158,207評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵误趴,是天一觀的道長(zhǎng)霹琼。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,755評(píng)論 1 284
  • 正文 為了忘掉前任迫肖,我火速辦了婚禮肌厨,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘诱贿。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,862評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布熄驼。 她就那樣靜靜地躺著像寒,像睡著了一般。 火紅的嫁衣襯著肌膚如雪瓜贾。 梳的紋絲不亂的頭發(fā)上诺祸,一...
    開(kāi)封第一講書(shū)人閱讀 50,050評(píng)論 1 291
  • 那天,我揣著相機(jī)與錄音祭芦,去河邊找鬼筷笨。 笑死,一個(gè)胖子當(dāng)著我的面吹牛龟劲,可吹牛的內(nèi)容都是我干的胃夏。 我是一名探鬼主播,決...
    沈念sama閱讀 39,136評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼昌跌,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼仰禀!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起蚕愤,我...
    開(kāi)封第一講書(shū)人閱讀 37,882評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤答恶,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后萍诱,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體悬嗓,經(jīng)...
    沈念sama閱讀 44,330評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,651評(píng)論 2 327
  • 正文 我和宋清朗相戀三年裕坊,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了包竹。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,789評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡碍庵,死狀恐怖映企,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情静浴,我是刑警寧澤堰氓,帶...
    沈念sama閱讀 34,477評(píng)論 4 333
  • 正文 年R本政府宣布,位于F島的核電站苹享,受9級(jí)特大地震影響双絮,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜得问,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,135評(píng)論 3 317
  • 文/蒙蒙 一囤攀、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧宫纬,春花似錦焚挠、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,864評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)榛泛。三九已至,卻和暖如春噩斟,著一層夾襖步出監(jiān)牢的瞬間曹锨,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,099評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工剃允, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留沛简,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,598評(píng)論 2 362
  • 正文 我出身青樓斥废,卻偏偏與公主長(zhǎng)得像椒楣,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子营袜,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,697評(píng)論 2 351

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

  • 寫(xiě)在題前:文章為本人原創(chuàng)撒顿, 如果文章轉(zhuǎn)載丑罪,必須標(biāo)明作者與出處荚板,并將原文鏈接以及github地址附在文章首行, 否則...
    SandyLoo閱讀 21,126評(píng)論 22 150
  • 前言 隨著公司業(yè)務(wù)的發(fā)展吩屹,數(shù)據(jù)的重要性日益體現(xiàn)出來(lái)跪另。 數(shù)據(jù)埋點(diǎn)的準(zhǔn)確和全面性顯得尤為重要。通過(guò)精準(zhǔn)和詳細(xì)的數(shù)據(jù)煤搜,后...
    MMR無(wú)與倫比閱讀 5,905評(píng)論 2 13
  • 國(guó)家電網(wǎng)公司企業(yè)標(biāo)準(zhǔn)(Q/GDW)- 面向?qū)ο蟮挠秒娦畔?shù)據(jù)交換協(xié)議 - 報(bào)批稿:20170802 前言: 排版 ...
    庭說(shuō)閱讀 10,934評(píng)論 6 13
  • 前言 最近跟同事花了點(diǎn)時(shí)間來(lái)思考可視化埋點(diǎn)免绿,并沒(méi)有什么突破性的進(jìn)展,不過(guò)市面上很多關(guān)于可視化埋點(diǎn)的技術(shù)文章都在講達(dá)...
    daixunry閱讀 7,997評(píng)論 1 38
  • 夢(mèng)項(xiàng)羽(七絕) 千秋大命歸黃土擦盾, 萬(wàn)里長(zhǎng)城漢闕樓嘲驾。 一曲虞姬歌舞地, 鴻門(mén)不見(jiàn)古今愁迹卢。
    商水閱讀 217評(píng)論 0 2