某硬件項目藍牙管理類VBBluetoothManager重構(gòu)

[toc]

業(yè)務背景

正如上篇iOS端智能硬件BLE通信技術(shù)實現(xiàn)一文所述亿傅,整個藍牙庫的最初設計實現(xiàn)最初都是為硬件通信服務的,19年年前突然接到需求皆愉,希望通過車載上放置的藍牙收音設備,讓用戶在喚醒設備后,直接將用戶的語音指令轉(zhuǎn)成app里面可執(zhí)行的操作叫倍,如“XXX, 播放每日XX的歌”,“XXX豺瘤,上/下一首”等一些當前app用戶常用的操作吆倦,產(chǎn)品希望通過這類車載設備來擴大app現(xiàn)有用戶的使用場景甚至探尋增加新用戶的可能。

現(xiàn)有問題

其實原app中藍牙通信目前已經(jīng)有兩套代碼了坐求,一套用于早先的藍牙耳機蚕泽,一套用于硬件A業(yè)務,硬件A業(yè)務由于是18年11月才并入現(xiàn)有app的桥嗤,存在兩套代碼也無可厚非须妻,現(xiàn)在又增加了新的車載設備,難道再編寫一套為車載設備而設計的藍牙通信方案嗎泛领?作為一個有節(jié)操的程序媛荒吏,當然不可能這么做了!渊鞋!
首先跟嵌入式端同學約定:車載設備的通信協(xié)議復用原有硬件A的那一套绰更,這樣數(shù)據(jù)收發(fā)處理(發(fā)送拆包接收組包)的代碼就能復用,但是負責管理藍牙一系列通信行為(掃描锡宋、停止掃描动知、連接、發(fā)現(xiàn)服務员辩、斷開連接盒粮、接收藍牙數(shù)據(jù)等)以及提供發(fā)送數(shù)據(jù)接口和回調(diào)收到的數(shù)據(jù)給用戶的VBBluetoothManager類, 耦合了掃描硬件A的標識符serviceUUID, 讀寫硬件A特征值的標識符characteristicUUID, 甚至有在已掃描到的設備列表中根據(jù)硬件名稱或UUID查找特定設備的接口,其中的很多接口都是為硬件A通信服務的奠滑,先看下原有部分代碼:

// from VBBluetoothManager.m, 以下...表示省略其他無關(guān)代碼
@interface VBBluetoothManager ()<VBDataBridgeDelegate,CBCentralManagerDelegate,CBPeripheralDelegate>
{
    ...
    NSArray *_serviceUUIDs;
    NSMutableArray *_characterUUIDs;
    ...
}

@implementation VBBluetoothManager

- (instancetype)init {
    if (self = [super init]) {
        ...
        _serviceUUIDs = @[[VBUUIDUtil UUIDWithType:VBUUIDService]];
        _characterUUIDs = [NSMutableArray array];
        NSArray<NSNumber *> *cases = [VBUUIDUtil cases];
        for (NSNumber *type in cases) {
            if ([type integerValue] != VBUUIDService) {
                [_characterUUIDs addObject:[VBUUIDUtil UUIDWithType:[type integerValue]]];
            }
        }
        ...
    }
    return self;
}

#pragma mark - Public Methods

// 掃描外設
- (void)scanWithDurationWithTimeout:(NSTimeInterval)timeout resetPrevScan:(BOOL)isReset {
    if (isReset) {
        [self resetScan];
    }
    // 前臺時不指定serviceUUID去掃描(兼容嵌入式舊版本丹皱,舊版本藍牙名稱很長妒穴,超過了藍牙廣播包的長度限制,導致根據(jù)serviceUUID無法掃描到)
    // 后臺的話bluetooth state reservation and restoration要求一定要指定serviceUUID
    NSArray *services = ([UIApplication sharedApplication].applicationState == UIApplicationStateActive) ? nil : _serviceUUIDs;
    [_central scanForPeripheralsWithServices: services options:@{CBCentralManagerScanOptionAllowDuplicatesKey: @NO}];
    // 先將之前的掃描取消
    [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(endScanForPeriphers) object:nil];
    [self performSelector:@selector(endScanForPeriphers) withObject:nil afterDelay:timeout];
}

// 取消所有連接
- (void)cancelAllConnections {
    _isManualDisconnect = YES;
    for (CBPeripheral *peripheral in [_central retrieveConnectedPeripheralsWithServices:_serviceUUIDs]) {
        [_central cancelPeripheralConnection:peripheral];
    }
}

// 設備連接成功后摊崭,在發(fā)送數(shù)據(jù)前先發(fā)現(xiàn)服務
- (void)startDiscoverServiceForPeripheral:(CBPeripheral *)peripheral {
    BOOL canSendData = [self canSendDataForPeripheral:peripheral];
    peripheral.delegate = self;
    if (canSendData) {
        [self constructDataBridges:peripheral];
        return;
    }
    [peripheral discoverServices:_serviceUUIDs];
}

#pragma mark - Private Methods

// 能否向外設發(fā)送數(shù)據(jù)
- (BOOL)canSendDataForPeripheral:(CBPeripheral *)peripheral {
    BOOL isRxCharacterNotify = NO;
    BOOL isCtsCharacterNotify = NO;
    for (CBCharacteristic *character in [peripheral.services.firstObject characteristics]) {
        VBUUIDType uuidType = [VBUUIDUtil typeForUUID:character.UUID];
        switch (uuidType) {
            case VBUUIDRxCharacteristic:
                isRxCharacterNotify = character.isNotifying;
                break;
            case VBUUIDCtsCharacteristic:
                isCtsCharacterNotify = character.isNotifying;
            default:
                break;
        }
    }
    
    // 只有在cts和rx通道都開啟的情況下讼油,才能發(fā)送數(shù)據(jù)
    BOOL canSendData = isRxCharacterNotify && isCtsCharacterNotify;
    return canSendData;
}

// 根據(jù)外設實例構(gòu)造數(shù)據(jù)加工處理的橋接類
- (void)constructDataBridges:(CBPeripheral *)peripheral {
    CBService *primaryService = peripheral.services.firstObject;
    NSArray<CBCharacteristic *> *characteristics = primaryService.characteristics;
    if (!characteristics) {
        return;
    }
    CBCharacteristic *txWriteCharacter;
    CBCharacteristic *rxReceiveCharacter;
    for (CBCharacteristic *character in characteristics) {
        VBUUIDType uuidType = [VBUUIDUtil typeForUUID:character.UUID];
        if (uuidType == VBUUIDNone) {
            break;
        } else if (uuidType == VBUUIDTxCharacteristic) {
            txWriteCharacter = character;
        } else {
            if (uuidType == VBUUIDRxCharacteristic) {
                rxReceiveCharacter = character;
            }
            if (!character.isNotifying) {
                [peripheral setNotifyValue:YES forCharacteristic:character];
            }
        }
    }
    
    if (txWriteCharacter && rxReceiveCharacter) {
        NSUInteger index = [self peripheralIndexAtDataBridges:peripheral];
        // 移除舊的
        if (index != NSNotFound) {
            [_dataBridges removeObjectAtIndex:index];
        }
        VBDataBridge *bridge = [[VBDataBridge alloc] initWithPeripheral:peripheral writeCharacter:txWriteCharacter receiveCharacter:rxReceiveCharacter];
        bridge.delegate = self;
        [_dataBridges addObject:bridge];
    }
}

#pragma mark - CBCentralManager delegate

- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *,id> *)advertisementData RSSI:(NSNumber *)RSSI {
    if (peripheral && ![_peripherals containsObject:peripheral] && [peripheral.name hasPrefix:@"硬件A的名稱"]) {
        [_peripherals addObject:peripheral];
        
        for (id<VBBluetoothManagerDelegate> observer in _delegateObservers) {
            NSAssert([NSThread isMainThread], @"非主線程");
            if ([observer respondsToSelector:@selector(bluetoothManager:didFindNewPeripheral:)]) {
                [observer bluetoothManager:self didFindNewPeripheral:peripheral];
            }
        }
    }
}

#pragma mark - CBPeripheral delegate

// 1. 找到服務
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error {
    NSArray<CBService *> *services = peripheral.services;
    if (!services) {
        return;
    }
    
    for (CBService *service in services) {
        [peripheral discoverCharacteristics:_characterUUIDs forService:service];
    }
}

// 2. 找到特征
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error {
    [self constructDataBridges:peripheral];
}

...

@end

從以上未重構(gòu)之前的VBBluetoothManager類的代碼可以看到:該類確實耦合了大量跟特定外設信息相關(guān)的代碼,導致現(xiàn)在新增加一種車載外設呢簸,卻無法走通同樣的通信流程矮台。

解決方案

1. 抽取公共屬性和方法

Untitled.gif

(1) ServiceUUIDs: 掃描外設時所用到的服務UUID數(shù)組;

(2) CharacterUUIDs: 讀寫數(shù)據(jù)特征和訂閱特征的UUID數(shù)組根时;

(3) rxCharacterUUID: 嵌入式端告知的讀數(shù)據(jù)的特征UUID瘦赫,和txCharacterUUID一起用于構(gòu)造數(shù)據(jù)加工處理類VBDataBridge實例,后者主要的作用是:收到數(shù)據(jù)處理的請求蛤迎,交由VBDataBridge去決定是該接收類receiver去組裝數(shù)據(jù)确虱,還是sender類去拆包分次發(fā)送數(shù)據(jù);

(4) txCharacterUUID: 嵌入式端告知的寫數(shù)據(jù)的特征UUID替裆,作用如上所述校辩;

(5) peripheral: CBPeripheral實例類, 用該實例包裝生成自己的藍牙外設類,判斷當前能否發(fā)送數(shù)據(jù)(canSendData)辆童、開啟訂閱特征(notifyCharacter)等都需從該實例中執(zhí)行相應操作宜咒;

(6) peripheralType: 外設類型,已知的外設類型枚舉把鉴;

(7) prefixName: 外設的名稱前綴荧呐;

(8) canSendData: 能否向外設發(fā)送數(shù)據(jù),只有寫數(shù)據(jù)通道處于notifying狀態(tài)才可寫纸镊;

(9) notifyCharacterForService:completionBlock::開啟可讀取或訂閱特征的通道倍阐,并把對應的特征值通過block傳回給調(diào)用方。

typedef void(^NotifyCharacterBlock)(CBCharacteristic *txWriteCharacter, CBCharacteristic *rxReceiveCharacter);

typedef NS_ENUM(NSInteger, VBPeripheralType) {
    VBPeripheralTypeUnknown,
    VBPeripheralTypeVbox,
    VBPeripheralTypeCarplay,
};

@protocol VBPeripheralProtocol <NSObject>

+ (NSString *)prefixName;

@end

// interface
@interface VBPeripheral : NSObject

@property (nonatomic, assign, readonly) VBPeripheralType type;
@property (nonatomic, strong) CBPeripheral *peripheral;
@property (nonatomic, copy, readonly) NSArray<CBUUID *> *services;
@property (nonatomic, copy, readonly) NSArray<CBUUID *> *characterUUIDs;
@property (nonatomic, copy, readonly) CBUUID *rxCharacterUUID;
@property (nonatomic, copy, readonly) CBUUID *txCharacterUUID;

+ (instancetype)peripheralWithCBPeripheral:(CBPeripheral *)peripheral;
- (BOOL)canSendData;
- (void)notifyCharactersForService:(CBService *)service completionBlock:(NotifyCharacterBlock)completion;

@end

// implementation
@implementation VBPeripheral

- (instancetype)initWithPeripheral:(CBPeripheral *)peripheral {
    
    self = [super init];
    if (self) {
        _peripheral = peripheral;
    }
    return self;
}

+ (instancetype)peripheralWithCBPeripheral:(CBPeripheral *)peripheral {
    if ([peripheral.name hasPrefix:[VBVboxPeripheral prefixName]]) {
        return [[VBVboxPeripheral alloc] initWithPeripheral:peripheral];
    } else if ([peripheral.name hasPrefix:[VBCarplayPeripheral prefixName]]) {
        return [[VBCarplayPeripheral alloc] initWithPeripheral:peripheral];
    } else {
        return nil;
    }
}

@end

2. 子類化對應外設模型

目前已知有兩種藍牙外設:硬件A和車載逗威,每種設備有自己的服務和特征UUID峰搪,我們需要在具體的外設模型中實現(xiàn)上面抽象出來的屬性和接口。

2.1 擴展VBUUIDType枚舉類型
typedef NS_ENUM(NSInteger, VBUUIDType)
{
    VBUUIDNone = 0,
    // 車載設備的UUID
    VBCarplayServiceType = 0xFE00,
    VBCarplayTxCharacteristicType= 0xFE01,
    VBCarplayRxCharacteristicType = 0xFE02,
    // 硬件設備的UUID
    VBVboxServiceType = 0xFFF0,
    VBVboxTxCharacteristicType = 0xFFF1, // 手機向硬件發(fā)送LE數(shù)據(jù)的鏈路
    VBVboxRxCharacteristicType = 0xFFF2, // 手機從硬件接收LE數(shù)據(jù)的鏈路
    VBVboxCtsCharacteristicType = 0xFFF3, // 標識手機是否可以繼續(xù)向硬件發(fā)送數(shù)據(jù)的鏈路,
};
2.2 硬件A外設模型
// interface
#import "VBPeripheralProtocol.h"
@interface VBVboxPeripheral :VBPeripheral

@end

// implementation
@implementation VBVboxPeripheral

- (VBPeripheralType)type {
    return VBPeripheralTypeVbox;
}

- (NSArray<CBUUID *> *)services {
    CBUUID *vboxService = [VBUUIDUtil UUIDWithType: VBVboxServiceType];
    return @[vboxService];
}

- (NSArray<CBUUID *> *)characterUUIDs {
    CBUUID *ctsCharacterUUID = [VBUUIDUtil UUIDWithType:VBVboxCtsCharacteristicType];
    return @[self.txCharacterUUID, self.rxCharacterUUID, ctsCharacterUUID];
}

- (CBUUID *)txCharacterUUID {
    return [VBUUIDUtil UUIDWithType:VBVboxTxCharacteristicType];
}

- (CBUUID *)rxCharacterUUID {
    return [VBUUIDUtil UUIDWithType:VBVboxRxCharacteristicType];
}

- (BOOL)canSendData {
    BOOL isRxCharacterNotify = NO;
    BOOL isCtsCharacterNotify = NO;
    for (CBCharacteristic *character in [self.peripheral.services.firstObject characteristics]) {
        VBUUIDType uuidType = [VBUUIDUtil typeForUUID:character.UUID];
        switch (uuidType) {
            case VBVboxRxCharacteristicType:
                isRxCharacterNotify = character.isNotifying;
                break;
            case VBVboxCtsCharacteristicType:
                isCtsCharacterNotify = character.isNotifying;
            default:
                break;
        }
    }
    
    // 硬件設備只有在cts和rx都開啟的情況下凯旭,才能發(fā)送數(shù)據(jù)
    BOOL canSendData = isRxCharacterNotify && isCtsCharacterNotify;
    return canSendData;
}

- (void)notifyCharactersForService:(CBService *)service completionBlock:(NotifyCharacterBlock)completion {
    CBCharacteristic *txWriteCharacter;
    CBCharacteristic *rxReceiveCharacter;
    for (CBCharacteristic *character in service.characteristics)
    {
        VBUUIDType uuidType = [VBUUIDUtil typeForUUID:character.UUID];
        switch (uuidType)
        {
            case VBVboxTxCharacteristicType:
                txWriteCharacter = character;
                break;
            case VBVboxRxCharacteristicType:
                rxReceiveCharacter = character;
                [self.peripheral setNotifyValue:YES forCharacteristic:character];
                break;
            case VBVboxCtsCharacteristicType:
                [self.peripheral setNotifyValue:YES forCharacteristic:character];
                break;
            default:
                break;
        }
    }
    
    completion(txWriteCharacter, rxReceiveCharacter);
}

+ (NSString *)prefixName {
    return @"硬件A的名稱";
}

@end
2.3 車載外設模型
// interface
#import "VBPeripheralProtocol.h"

@interface VBCarplayPeripheral :VBPeripheral

@end

// implementation
@implementation VBCarplayPeripheral

- (VBPeripheralType)type {
    return VBPeripheralTypeCarplay;
}

- (NSArray<CBUUID *> *)services {
    CBUUID *carplayService = [VBUUIDUtil UUIDWithType:VBCarplayServiceType];
    return @[carplayService];
}

- (NSArray<CBUUID *> *)characterUUIDs {
    return @[self.txCharacterUUID, self.rxCharacterUUID];
}

- (CBUUID *)txCharacterUUID {
    return [VBUUIDUtil UUIDWithType:VBCarplayTxCharacteristicType];
}

- (CBUUID *)rxCharacterUUID {
    return [VBUUIDUtil UUIDWithType:VBCarplayRxCharacteristicType];
}

- (BOOL)canSendData {
    // 車載設備只要rx通道打開就能發(fā)送數(shù)據(jù)
    BOOL isRxNotifying = NO;
    for (CBCharacteristic *character in [self.peripheral.services.firstObject characteristics]) {
        VBUUIDType uuidType = [VBUUIDUtil typeForUUID:character.UUID];
        if (uuidType == VBCarplayRxCharacteristicType) {
            isRxNotifying = character.isNotifying;
            break;
        }
    }
    
    return isRxNotifying;
}

- (void)notifyCharactersForService:(CBService *)service completionBlock:(NotifyCharacterBlock)completion {
    CBCharacteristic *txWriteCharacter;
    CBCharacteristic *rxReceiveCharacter;
    for (CBCharacteristic *character in service.characteristics)
    {
        VBUUIDType uuidType = [VBUUIDUtil typeForUUID:character.UUID];
        switch (uuidType)
        {
            case VBCarplayTxCharacteristicType:
                txWriteCharacter = character;
                break;
            case VBCarplayRxCharacteristicType:
                rxReceiveCharacter = character;
                [self.peripheral setNotifyValue:YES forCharacteristic:character];
                break;
            default:
                break;
        }
    }
    completion(txWriteCharacter, rxReceiveCharacter);
}

+ (NSString *)prefixName {
    return @"NeVSPS";
}

3. 重構(gòu)VBBluetoothManager

VBBluetoothManager類的原有代碼中概耻,替換原先用到serviceUUIDs、characterUUIDs以及能否判斷能否向外設發(fā)送代碼的業(yè)務邏輯:

@interface VBBluetoothManager ()
{
    NSArray<CBUUID *> *_serviceUUIDs;
}
@end

@implementation VBBluetoothManager 

- (instancetype)init {
    self = [super init];
    if (self) {
    ...
        _serviceUUIDs = @[
                          [VBUUIDUtil UUIDWithType: VBVboxServiceType],
                          [VBUUIDUtil UUIDWithType: VBCarplayServiceType]];
    ...
    }
    return self;
}

- (void)startDiscoverServiceForPeripheral:(CBPeripheral *)peripheral {
    BOOL canSendData = [self canSendDataForPeripheral:peripheral];
    NELogVerbose(@"%s %@", __func__, @(canSendData));
    peripheral.delegate = self;
    id<VBPeripheralProtocol> vbPeripheral = [VBPeripheral peripheralWithCBPeripheral:peripheral];
    if ([vbPeripheral canSendData]) {
        return;
    }
    
    [peripheral discoverServices:vbPeripheral.services];
}

- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *,id> *)advertisementData RSSI:(NSNumber *)RSSI {
    BOOL isVboxPeripheral = [peripheral.name hasPrefix:[VBVboxPeripheral prefixName]];
    BOOL isCarplayPeripheral = [peripheral.name hasPrefix:[VBCarplayPeripheral prefixName]];
    if (peripheral && ![_peripherals containsObject:peripheral] &&
        (isVboxPeripheral || isCarplayPeripheral)) {
        [_peripherals addObject:peripheral];
        
        for (id<VBBluetoothManagerDelegate> observer in _delegateObservers) {
            NSAssert([NSThread isMainThread], @"非主線程");
            if ([observer respondsToSelector:@selector(bluetoothManager:didFindNewPeripheral:)]) {
                [observer bluetoothManager:self didFindNewPeripheral:peripheral];
            }
        }
    }
}

#pragma mark - CBPeripheral delegate

// 1. 找到服務
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error {
    id<VBPeripheralProtocol> vbPeripheral = [VBPeripheral peripheralWithCBPeripheral:peripheral];
    for (CBService *service in peripheral.services) {
        [peripheral discoverCharacteristics:vbPeripheral.characterUUIDs forService:service];
    }
}

// 2. 找到特征
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error {
    id<VBPeripheralProtocol> vbPeripheral = [VBPeripheral peripheralWithCBPeripheral:peripheral];
    [vbPeripheral notifyCharactersForService:service completionBlock:^(CBCharacteristic *txWriteCharacter, CBCharacteristic *rxReceiveCharacter) {
        if (txWriteCharacter && rxReceiveCharacter) {
            NSUInteger index = [self peripheralIndexAtDataBridges:peripheral];
            // 移除舊的
            if (index != NSNotFound) {
                [_dataBridges removeObjectAtIndex:index];
            }
            VBDataBridge *bridge = [[VBDataBridge alloc] initWithPeripheral:peripheral writeCharacter:txWriteCharacter receiveCharacter:rxReceiveCharacter];
            bridge.delegate = self;
            [_dataBridges addObject:bridge];
        }
    }];
}

...
@end

至此罐呼,重構(gòu)藍牙管理類以支持多種設備的藍牙數(shù)據(jù)通信工作就完成了鞠柄,至于接收到數(shù)據(jù)之后,上層怎么處理又是另外一回事了嫉柴,在此不談厌杜。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子夯尽,更是在濱河造成了極大的恐慌瞧壮,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件匙握,死亡現(xiàn)場離奇詭異咆槽,居然都是意外死亡,警方通過查閱死者的電腦和手機圈纺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進店門秦忿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人蛾娶,你說我怎么就攤上這事灯谣。” “怎么了茫叭?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長半等。 經(jīng)常有香客問我揍愁,道長,這世上最難降的妖魔是什么杀饵? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任莽囤,我火速辦了婚禮,結(jié)果婚禮上切距,老公的妹妹穿的比我還像新娘朽缎。我一直安慰自己,他們只是感情好谜悟,可當我...
    茶點故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布话肖。 她就那樣靜靜地躺著,像睡著了一般葡幸。 火紅的嫁衣襯著肌膚如雪最筒。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天蔚叨,我揣著相機與錄音床蜘,去河邊找鬼。 笑死蔑水,一個胖子當著我的面吹牛邢锯,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播搀别,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼丹擎,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了歇父?” 一聲冷哼從身側(cè)響起鸥鹉,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤蛮穿,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后毁渗,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體践磅,經(jīng)...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年灸异,在試婚紗的時候發(fā)現(xiàn)自己被綠了府适。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,727評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡肺樟,死狀恐怖檐春,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情么伯,我是刑警寧澤疟暖,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站田柔,受9級特大地震影響俐巴,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜硬爆,卻給世界環(huán)境...
    茶點故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一欣舵、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧缀磕,春花似錦缘圈、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至牲剃,卻和暖如春糊饱,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背颠黎。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工另锋, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人狭归。 一個月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓夭坪,卻偏偏與公主長得像,于是被迫代替她去往敵國和親过椎。 傳聞我的和親對象是個殘疾皇子室梅,可洞房花燭夜當晚...
    茶點故事閱讀 44,619評論 2 354