Swift | 實(shí)現(xiàn)UI組件的點(diǎn)擊事件全埋點(diǎn)

BSGestureRecognizerForwarder

final class BSGestureRecognizerForwarder: NSObject {
    weak var target: UIResponder?
    var action: Selector?

    init(target: UIResponder?, action: Selector?) {
        self.target = target
        self.action = action
    }

    @objc func swizzledAction(for sender: UIGestureRecognizer) {
        guard let target = self.target, let action = self.action else {
            return
        }

        if let gesture = sender as? UITapGestureRecognizer {
            Logger.shared.track(UIControlEvent(date: .init(), object: self, action: action, params: [
                "Action": action,
                "Target": target,
                "Event": "UITapGestureRecognizer",
                "Gesture": gesture
            ]))
        }

        if target.responds(to: action), target.canPerformAction(action, withSender: nil) {
            target.perform(action, with: nil)
        }
    }
}

struct UIResponderHooker {
    static var touchesBeganIMP: IMP!
}

extension UIResponder {
    private static let dispatchOnce: Void = {
        if let method = class_getInstanceMethod(UIResponder.self, #selector(touchesBegan(_:with:))) {
            UIResponderHooker.touchesBeganIMP = method_getImplementation(method)
        }
        switchSelector(#selector(UIResponder.touchesBegan(_:with:)), #selector(UIResponder.self_touchesBegan(_:with:)))
    }()

    @objc class func startupTracking() {
        dispatchOnce
    }

    @objc func self_touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        typealias ClosureType = @convention(c) (UIResponder, Selector, Set<UITouch>, UIEvent?) -> Void
        let touchesBegan: ClosureType = unsafeBitCast(UIResponderHooker.touchesBeganIMP, to: ClosureType.self)
        touchesBegan(self, #selector(touchesBegan(_:with:)), touches, event)

        guard !touches.isEmpty, let event = event else {
            return
        }

        let className = NSStringFromClass(type(of: self))
        let containsFilteredName: Bool = [
            "AppDelegate", "UIApplication", "UIWindow", "UITransitionView",
            "UIStackView", "UITableViewCellContentView", "UIDropShadowView"
        ].first(where: {
            className.contains($0)
        }) != nil

        if className.hasPrefix("MobileDesign") ||
            (!className.hasPrefix("_") && !containsFilteredName) {
            Logger.shared.track(UIControlEvent(date: .init(), object: self, action: #selector(touchesBegan(_:with:)), params: [
                "Touches": touches,
                "Event": event
            ]))
        }
    }
}

extension UIResponder {
    private enum ASKeys {
        static var bsGestureActionsKey = "GestureActionsKey"
    }

    var bsGestureActions: [BSGestureRecognizerForwarder]? {
        get {
            objc_getAssociatedObject(self, &ASKeys.bsGestureActionsKey) as? [BSGestureRecognizerForwarder]
        }
        set {
            objc_setAssociatedObject(self, &ASKeys.bsGestureActionsKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
        }
    }
}

extension UITapGestureRecognizer {
    private static let dispatchOnce: Void = {
        switchSelector(#selector(Self.init(target:action:)), #selector(Self.self_init(target:action:)))
        switchSelector(#selector(Self.addTarget(_:action:)), #selector(Self.self_addTarget(_:action:)))
        switchSelector(#selector(Self.removeTarget(_:action:)), #selector(Self.self_removeTarget(_:action:)))
    }()

    @objc class func startupTracking() {
        guard self == UITapGestureRecognizer.self else {
            return
        }
        dispatchOnce
    }

    @objc func self_init(target: Any?, action: Selector?) -> UIGestureRecognizer {
        if let responder = target as? UIResponder {
            if responder.bsGestureActions == nil {
                responder.bsGestureActions = []
            }

            let forwarder = BSGestureRecognizerForwarder(target: responder, action: action)
            responder.bsGestureActions?.append(forwarder)
            return self_init(target: forwarder, action: #selector(forwarder.swizzledAction(for:)))
        }
        return self_init(target: target, action: action)
    }

    @objc func self_addTarget(_ target: Any, action: Selector) {
        guard let responder = target as? UIResponder else {
            return self_addTarget(target, action: action)
        }
        if responder.bsGestureActions == nil {
            responder.bsGestureActions = []
        }

        let forwarder = BSGestureRecognizerForwarder(target: responder, action: action)
        responder.bsGestureActions?.append(forwarder)
        self_addTarget(forwarder, action: #selector(forwarder.swizzledAction(for:)))
    }

    @objc func self_removeTarget(_ target: Any?, action: Selector?) {
        if let responder = target as? UIResponder,
            let action = action,
            let actions = responder.bsGestureActions {
            for index in 0 ..< actions.count {
                let ac = actions[index]
                guard let bsTarget = ac.target, let bsAction = ac.action else {
                    continue
                }
                guard let responderClass = object_getClass(responder) else {
                    continue
                }
                let responderClassName = NSStringFromClass(responderClass)

                guard let targetClass = object_getClass(bsTarget) else {
                    continue
                }
                let targetClassName = NSStringFromClass(targetClass)

                if responderClassName == targetClassName, action.description == bsAction.description {
                    responder.bsGestureActions?.remove(at: index)
                    break
                }
            }
        }
        self_removeTarget(target, action: action)
    }
}

extension UIControl {
    private static let dispatchOnce: Void = {
        switchSelector(#selector(UIControl.sendAction(_:to:for:)), #selector(UIControl.self_sendAction(_:to:for:)))
    }()

    @objc override class func startupTracking() {
        dispatchOnce
    }

    @objc private func self_sendAction(_ action: Selector, to target: Any?, for event: UIEvent?) {
        Logger.shared.track(UIControlEvent(date: .init(), object: self, action: action, params: [
            "Action": action,
            "Target": target ?? "empty-target",
            "Event": event ?? "empty-event"
        ]))

        self_sendAction(action, to: target, for: event)
    }
}

extension UIViewController {
    @objc override class func startupTracking() {
        let selfViewWillAppearSelector = #selector(self_viewWillAppear(_:))
        let orgViewWillAppearSelector = #selector(viewWillAppear(_:))
        switchSelector(orgViewWillAppearSelector, selfViewWillAppearSelector)

        let selfViewDidAppearSelector = #selector(self_viewDidAppear(_:))
        let orgViewDidAppearSelector = #selector(viewDidAppear(_:))
        switchSelector(orgViewDidAppearSelector, selfViewDidAppearSelector)

        let selfViewWillLayoutSubviewsSelector = #selector(self_viewWillLayoutSubviews)
        let orgViewWillLayoutSubviewsSelector = #selector(viewWillLayoutSubviews)
        switchSelector(orgViewWillLayoutSubviewsSelector, selfViewWillLayoutSubviewsSelector)

        let selfViewDidLayoutSubviewsSelector = #selector(self_viewDidLayoutSubviews)
        let orgViewDidLayoutSubviewsSelector = #selector(viewDidLayoutSubviews)
        switchSelector(orgViewDidLayoutSubviewsSelector, selfViewDidLayoutSubviewsSelector)
    }

    @objc private func self_viewWillAppear(_ animated: Bool) {
        self_viewWillAppear(animated)

        Logger.shared.track(UIViewControllerEvent(lifeCycle: .viewWillAppear, date: .init(), object: self, action: NSSelectorFromString(#function), params: [
            "animated": animated
        ]))
    }

    @objc private func self_viewDidAppear(_ animated: Bool) {
        self_viewDidAppear(animated)
        Logger.shared.track(UIViewControllerEvent(lifeCycle: .viewDidAppear, date: .init(), object: self, action: NSSelectorFromString(#function), params: [
            "animated": animated
        ]))
    }

    @objc private func self_viewWillLayoutSubviews() {
        self_viewWillLayoutSubviews()
        Logger.shared.track(UIViewControllerEvent(lifeCycle: .viewWillLayoutSubviews, date: .init(), object: self, action: NSSelectorFromString(#function), params: ""))
    }

    @objc private func self_viewDidLayoutSubviews() {
        self_viewDidLayoutSubviews()
        Logger.shared.track(UIViewControllerEvent(lifeCycle: .viewDidLayoutSubviews, date: .init(), object: self, action: NSSelectorFromString(#function), params: ""))
    }
}

struct UIViewControllerEvent: TrackableEvent {
    enum LifeCycle {
        case viewDidLoad
        case viewWillAppear, viewDidAppear
        case viewWillLayoutSubviews, viewDidLayoutSubviews
    }

    var lifeCycle: LifeCycle
    var date: Date
    var object: CustomStringConvertible
    var category: Logger.TrackingCategory {
        switch lifeCycle {
        case .viewWillAppear: return .display(subevent: .willAppear)
        case .viewDidAppear: return .display(subevent: .didAppear)
        case .viewWillLayoutSubviews: return .layout(subevent: .willLayoutSubviews)
        case .viewDidLayoutSubviews: return .layout(subevent: .didLayoutSubviews)
        default:
            return .unspecified
        }
    }

    var action: Selector
    var params: CustomStringConvertible
}

struct UIControlEvent: TrackableEvent {
    var date: Date
    var object: CustomStringConvertible
    var category: Logger.TrackingCategory {
        guard let control = object as? UIControl else {
            return .unspecified
        }
        switch control.allControlEvents {
        case .touchUpInside:
            return .touching(subevent: .upInside)
        default:
            return .touching(subevent: .any)
        }
    }

    var action: Selector
    var params: CustomStringConvertible
}

class AppDelegate: AppControllingDelegate {
    private lazy var debugShortcutHandler = Bundle.entityBundleHasMultipleEnvironments ? DebugEnvironmentHandlingModel() : nil

    lazy var deepLinkProvider = DeepLinkProvider()
    lazy var appControllerRouter = AppControllerRouter(
        featuresConfig: featuresConfig,
        debugShortcutHandler: debugShortcutHandler,
        deepLinkProvider: deepLinkProvider
    )

    override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        UIResponder.startupTracking()
        UIControl.startupTracking()
        UIViewController.startupTracking()
        UITapGestureRecognizer.startupTracking()
        URLSession.startTracking()
        setup()
        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
    }

    override func makeAppController() -> AppControllerTemplate {
        appControllerRouter.routingAppController
    }
}

extension AppDelegate {
    private func setup() {
        let config = AppSetupConfiguration(
            entityBundle: Bundle.firstEntityBundle,
            featuresConfig: featuresConfig,
            appInitializationStrategyFactory: appInitializationStrategyFactory,
            deepLinkProvider: deepLinkProvider,
            appLaunch: featuresConfig.preAppBundleInitConfig.appLaunch,
            firebaseWrapper: firebaseWrapper,
            debugShortcutHandler: debugShortcutHandler,
            widgetRegistrationProvidings: widgetRegistrationProvidings,
            keychainItemMigratorProvider: featuresConfig.preAppBundleInitConfig.keychainItemMigratorProvider
        )

        let steps = AppSetupStepsFactory.getDefaultSteps(config: config)
        steps.executeAll()
        appControllerRouter.route(to: .rasp)
    }
}

extension AppDelegate: QuickActionShortCutsHandler {
    public func resetQuickActionShortCut() {
        if let quickActionShortcutStep = appControllerRouter.quickActionShortcutStep {
            quickActionShortcutStep.execute()
        }
    }
}

extension URLSession {
    static let sizzle: Void = {
        let orgMethod = #selector(URLSession.shared.dataTask(with:completionHandler:) as ((URLRequest, @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask))
        switchSelector(orgMethod, #selector(swizzle_dataTask(with:completionHandler:)))
    }()

    @objc class func startTracking() {
        guard self == URLSession.self else {
            return
        }
        sizzle
    }

    @objc private func swizzle_dataTask(with request: URLRequest, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask {
        let requestDate = Date()
        Logger.shared.track(NetworkEvent(object: self, date: requestDate, category: .network(subevent: .didSendRequest), action: NSSelectorFromString(#function), params: TrackableRequest(request: request)))

        return swizzle_dataTask(with: request) { data, response, error in
            if let response = response as? HTTPURLResponse {
                let duration = fabs(requestDate.timeIntervalSinceNow)

                let cate: Logger.TrackingCategory.Networking = error != nil
                    ? .didReceiveResponseWithError
                    : .didReceiveResponse

                Logger.shared.track(NetworkEvent(object: self, date: .init(), category: .network(subevent: cate), action: NSSelectorFromString(#function), params: TrackableResponse(duration: duration, data: data, response: response, error: error)))
            }

            completionHandler(data, response, error)
        }
    }
}

extension NSObject {
    static func switchSelector(_ originalSelector: Selector, _ swizzledSelector: Selector) {
        guard
            let originalMethod = class_getInstanceMethod(self, originalSelector),
            let swizzledMethod = class_getInstanceMethod(self, swizzledSelector) else {
            return
        }
        let didAddMethod: Bool = class_addMethod(self, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))
        if didAddMethod {
            class_replaceMethod(self, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod))
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod)
        }
    }
}

struct TrackableRequest: Requestable, CustomStringConvertible {
    let request: URLRequest

    init(request: URLRequest) {
        self.request = request
    }

    public var urlString: String {
        request.url?.absoluteString ?? "empty-url"
    }

    public var headers: [String: String] {
        request.allHTTPHeaderFields ?? [:]
    }

    public var methodString: String {
        request.httpMethod ?? "empty-method"
    }

    var description: String {
        "[Method: \(methodString), URL: \(urlString), Headers: \(headers)]"
    }
}

struct TrackableResponse: Responsable, CustomStringConvertible {
    var duration: Double
    var data: Data?
    var response: HTTPURLResponse
    var error: Error?

    public var urlString: String {
        response.url?.absoluteString ?? "empty-url"
    }

    public var errorString: String? {
        error?.localizedDescription
    }

    public var statusCode: Int {
        response.statusCode
    }

    private let NF: NumberFormatter = {
        let nf = NumberFormatter()
        nf.maximumFractionDigits = 2
        return nf
    }()

    var description: String {
        let duration = NF.string(from: .init(value: duration)) ?? "NaN"
        let base = "[Duration: \(duration)s, Status: \(statusCode), URL: \(urlString)"
        if let error = errorString {
            return base + ", Error: \(error)]"
        }
        if let data = data {
            return base + ", Data: \(data.description)]"
        }
        return base
    }
}

public struct NetworkEvent: TrackableEvent {
    public var object: CustomStringConvertible
    public var date: Date
    public var category: Logger.TrackingCategory
    public var action: Selector
    public var params: CustomStringConvertible
}

let df = DateFormatter()
public struct Logger {
    public static var shared = Logger()
    private static let oslog = OSLog(subsystem: Bundle.main.bundleIdentifier ?? "no_bundle_id", category: "UITracking")

    var savePath: String {
        df.locale = Calendar.current.locale
        df.dateFormat = "yyyy-MM-dd"
        let name = "BS_Track_Log_" + df.string(from: .init()) + ".log"
        return "\(NSHomeDirectory())/Documents/\(name)"
    }

    var saveURL: URL {
        .init(fileURLWithPath: savePath)
    }

    lazy var logsString: String = {
        if FileManager.default.fileExists(atPath: savePath),
            let data = try? Data(contentsOf: saveURL),
            let logs = String(data: data, encoding: .utf8) {
            return logs
        }
        return ""
    }()

    public mutating func track(_ event: TrackableEvent) {
        if !logsString.isEmpty {
            logsString += "\n" + event.description
        } else {
            logsString = event.description
        }

        // write to system logs
        var logType = OSLogType.info
        if event.category == .network(subevent: .didReceiveResponseWithError) {
            logType = .error
        }
        os_log(logType, log: Logger.oslog, "%{public}@", event.description)

        #if DEBUG
            print("BS_Track_Log path: \(savePath)")
            print(logsString)
        #endif

        // write to sandbox logs
        guard let data = logsString.data(using: .utf8) else {
            return
        }
        do {
            try data.write(to: saveURL, options: .atomic)
        } catch {
            print(error)
        }
    }
}

public extension Logger {
    enum TrackingCategory {
        case unspecified
        case tracking(subevent: Tracking)
        case touching(subevent: Touching)
        case display(subevent: Displaying)
        case layout(subevent: Layouting)
        case network(subevent: Networking)
    }
}

extension Logger.TrackingCategory: Equatable {
    public static func == (lhs: Self, rhs: Self) -> Bool {
        switch (lhs, rhs) {
        case (.unspecified, .unspecified),
             (.tracking(.start), .tracking(.start)),
             (.tracking(.end), .tracking(.end)),
             (.touching(.any), .touching(.any)),
             (.display(.willAppear), .display(.willAppear)),
             (.display(.didAppear), .display(.didAppear)),
             (.layout(.willLayoutSubviews), .layout(.willLayoutSubviews)),
             (.layout(.didLayoutSubviews), .layout(.didLayoutSubviews)),
             (.network(.didSendRequest), .network(.didSendRequest)),
             (.network(.didReceiveResponse), .network(.didReceiveResponse)),
             (.network(.didReceiveResponseWithError), .network(.didReceiveResponseWithError)):
            return true
        default: return false
        }
    }
}

public extension Logger.TrackingCategory {
    enum Tracking {
        case start, end
    }

    enum Touching {
        case any, upInside
    }

    enum Displaying {
        case willAppear, didAppear
    }

    enum Layouting {
        case willLayoutSubviews
        case didLayoutSubviews
    }

    enum Networking {
        case didSendRequest
        case didReceiveResponse
        case didReceiveResponseWithError
    }
}

extension Logger.TrackingCategory: CustomStringConvertible {
    public var description: String {
        switch self {
        case .unspecified: return "Unspecified"
        case .tracking(let subevent):
            switch subevent {
            case .start: return "Tracking->Start"
            case .end: return "Tracking->End"
            }
        case .touching(let subevent):
            switch subevent {
            case .any: return "Touching->Any"
            case .upInside: return "Touching->UpInside"
            }
        case .layout(let subevent):
            switch subevent {
            case .willLayoutSubviews:
                return "Layout->WillLayoutSubviews"
            case .didLayoutSubviews:
                return "Layout->DidLayoutSubviews"
            }
        case .display(let subevent):
            switch subevent {
            case .willAppear:
                return "Display->WillAppear"
            case .didAppear:
                return "Display->DidAppear"
            }
        case .network(let subevent):
            switch subevent {
            case .didSendRequest:
                return "Network->DidSendRequest"
            case .didReceiveResponse:
                return "Network->DidReceiveResponse"
            case .didReceiveResponseWithError:
                return "Network->DidReceiveResponseWithError"
            }
        }
    }
}

public protocol Requestable {
    var headers: [String: String] { get }
    var urlString: String { get }
    var methodString: String { get }
}

public protocol Responsable {
    var urlString: String { get }
    var errorString: String? { get }
    var statusCode: Int { get }
}

public protocol TrackableEvent {
    var category: Logger.TrackingCategory { get }
    var object: CustomStringConvertible { get set }
    var date: Date { get set }
    var action: Selector { get set }
    var params: CustomStringConvertible { get set }
}

extension TrackableEvent {
    public var comment: String {
        switch category {
        case .unspecified: return "An undefined event was fired"
        case .tracking(let subevent):
            switch subevent {
            case .start: return "A test case tracking is started"
            case .end: return "A test case tracking is ended"
            }
        case .touching(let subevent):
            switch subevent {
            case .any: return "An any touch event was fired"
            case .upInside: return "An user touch up inside event was fired"
            }
        case .layout(let subevent):
            switch subevent {
            case .willLayoutSubviews:
                return "A page will layout subviews"
            case .didLayoutSubviews:
                return "A page finished layouting subviews"
            }
        case .display(let subevent):
            switch subevent {
            case .willAppear:
                return "A page will be displayed on screen"
            case .didAppear:
                return "A page is displayed on screen"
            }
        case .network(let subevent):
            switch subevent {
            case .didSendRequest:
                return "An HTTP request was sent"
            case .didReceiveResponse:
                return "An HTTP response is received"
            case .didReceiveResponseWithError:
                return "An HTTP response is received with error"
            }
        }
    }
}

extension TrackableEvent {
    public var description: String {
        df.locale = Calendar.current.locale
        df.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS"
        let dateString = df.string(from: date)
        return "[\(dateString)] [\(comment)] [\(category)] [\(object)->\(action)->\(params)]"
    }
}

#!/bin/bash

components=(
ActiveInactiveLabel
)
num=0

echo "" > GDLRefs.txt

echo "Pulling git updates..."
git pull

for component in ${components[*]}
do
    num=`expr $num + 1`
    echo "Searching for $component: $num/${#components[*]}"
    echo "$component: " >> GDLRefs.txt

    refs=`find iOS -name *.swift -exec grep $component -rlnws {} \;`
    refs=`echo $refs | tr -d '[ ]'`
    refs=${refs//iOS/ iOS}
    for ref in ${refs[*]}
    do
        echo $ref
        echo $ref >> GDLRefs.txt
    done
    echo " " >> GDLRefs.txt
done

echo "Done of searchings."

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末旬陡,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子鲁纠,更是在濱河造成了極大的恐慌泌参,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件缨称,死亡現(xiàn)場離奇詭異腹暖,居然都是意外死亡酪耳,警方通過查閱死者的電腦和手機(jī)抢呆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門煮嫌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人抱虐,你說我怎么就攤上這事昌阿。” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵宝泵,是天一觀的道長。 經(jīng)常有香客問我轩娶,道長儿奶,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任鳄抒,我火速辦了婚禮闯捎,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘许溅。我一直安慰自己瓤鼻,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布贤重。 她就那樣靜靜地躺著茬祷,像睡著了一般。 火紅的嫁衣襯著肌膚如雪并蝗。 梳的紋絲不亂的頭發(fā)上祭犯,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音滚停,去河邊找鬼沃粗。 笑死,一個(gè)胖子當(dāng)著我的面吹牛键畴,可吹牛的內(nèi)容都是我干的最盅。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼起惕,長吁一口氣:“原來是場噩夢啊……” “哼涡贱!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起惹想,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤盼产,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后勺馆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體戏售,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年草穆,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了灌灾。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡悲柱,死狀恐怖锋喜,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤嘿般,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布段标,位于F島的核電站,受9級(jí)特大地震影響炉奴,放射性物質(zhì)發(fā)生泄漏逼庞。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一瞻赶、第九天 我趴在偏房一處隱蔽的房頂上張望赛糟。 院中可真熱鬧,春花似錦砸逊、人聲如沸璧南。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽司倚。三九已至,卻和暖如春篓像,著一層夾襖步出監(jiān)牢的瞬間对湃,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來泰國打工遗淳, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留拍柒,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓屈暗,卻偏偏與公主長得像拆讯,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子养叛,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

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