SpringBoot:切面AOP實現(xiàn)權限校驗:實例演示與注解全解

1 理解AOP

1.1 什么是AOP

AOP(Aspect Oriented Programming)红碑,面向切面思想蔑穴,是Spring的三大核心思想之一(兩外兩個:IOC-控制反轉(zhuǎn)、DI-依賴注入)。

那么AOP為何那么重要呢叙量?在我們的程序中,經(jīng)常存在一些系統(tǒng)性的需求,比如權限校驗扑馁、日志記錄效诅、統(tǒng)計等篡腌,這些代碼會散落穿插在各個業(yè)務邏輯中,非常冗余且不利于維護米死。例如下面這個示意圖:

有多少業(yè)務操作物喷,就要寫多少重復的校驗和日志記錄代碼卤材,這顯然是無法接受的。當然峦失,用面向?qū)ο蟮乃枷肷却裕覀兛梢园堰@些重復的代碼抽離出來,寫成公共方法尉辑,就是下面這樣:

這樣晕拆,代碼冗余和可維護性的問題得到了解決,但每個業(yè)務方法中依然要依次手動調(diào)用這些公共方法,也是略顯繁瑣实幕。有沒有更好的方式呢吝镣?有的,那就是AOP昆庇,AOP將權限校驗末贾、日志記錄等非業(yè)務代碼完全提取出來,與業(yè)務代碼分離整吆,并尋找節(jié)點切入業(yè)務代碼中:

1.2 AOP體系與概念

簡單地去理解拱撵,其實AOP要做三類事:

  • 在哪里切入,也就是權限校驗等非業(yè)務操作在哪些業(yè)務代碼中執(zhí)行表蝙。
  • 在什么時候切入拴测,是業(yè)務代碼執(zhí)行前還是執(zhí)行后。
  • 切入后做什么事府蛇,比如做權限校驗集索、日志記錄等。

因此汇跨,AOP的體系可以梳理為下圖:

一些概念詳解:

  • Pointcut:切點务荆,決定處理如權限校驗、日志記錄等在何處切入業(yè)務代碼中(即織入切面)穷遂。切點分為execution方式和annotation方式函匕。前者可以用路徑表達式指定哪些類織入切面,后者可以指定被哪些注解修飾的代碼織入切面蚪黑。
  • Advice:處理盅惜,包括處理時機和處理內(nèi)容。處理內(nèi)容就是要做什么事忌穿,比如校驗權限和記錄日志抒寂。處理時機就是在什么時機執(zhí)行處理內(nèi)容,分為前置處理(即業(yè)務代碼執(zhí)行前)伴网、后置處理(業(yè)務代碼執(zhí)行后)等。
  • Aspect:切面妆棒,即PointcutAdvice澡腾。
  • Joint point:連接點,是程序執(zhí)行的一個點糕珊。例如动分,一個方法的執(zhí)行或者一個異常的處理。在 Spring AOP 中红选,一個連接點總是代表一個方法執(zhí)行澜公。
  • Weaving:織入,就是通過動態(tài)代理,在目標對象方法中執(zhí)行處理內(nèi)容的過程坟乾。

網(wǎng)絡上有張圖迹辐,我覺得非常傳神,貼在這里供大家觀詳:

2 AOP實例

實踐出真知甚侣,接下來我們就擼代碼來實現(xiàn)一下AOP明吩。完成項目已上傳至:

https://github.com/ThinkMugz/aopDemo

使用 AOP,首先需要引入 AOP 的依賴殷费。參數(shù)校驗:這么寫參數(shù)校驗(validator)就不會被勸退了~

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

2.1 第一個實例

接下來印荔,我們先看一個極簡的例子:所有的get請求被調(diào)用前在控制臺輸出一句"get請求的advice觸發(fā)了"。

具體實現(xiàn)如下:

  1. 創(chuàng)建一個AOP切面類详羡,只要在類上加個 @Aspect 注解即可。@Aspect 注解用來描述一個切面類,定義切面類的時候需要打上這個注解副硅。@Component 注解將該類交給 Spring 來管理拯杠。在這個類里實現(xiàn)advice:
package com.mu.demo.advice;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class LogAdvice {
    // 定義一個切點:所有被GetMapping注解修飾的方法會織入advice
    @Pointcut("@annotation(org.springframework.web.bind.annotation.GetMapping)")
    private void logAdvicePointcut() {}

 // Before表示logAdvice將在目標方法執(zhí)行前執(zhí)行
    @Before("logAdvicePointcut()")
    public void logAdvice(){
     // 這里只是一個示例,你可以寫任何處理邏輯
        System.out.println("get請求的advice觸發(fā)了");
    }
}
  1. 創(chuàng)建一個接口類主到,內(nèi)部創(chuàng)建一個get請求:
package com.mu.demo.controller;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping(value = "/aop")
public class AopController {
    @GetMapping(value = "/getTest")
    public JSONObject aopTest() {
        return JSON.parseObject("{\"message\":\"SUCCESS\",\"code\":200}");
    }

 @PostMapping(value = "/postTest")
    public JSONObject aopTest2(@RequestParam("id") String id) {
        return JSON.parseObject("{\"message\":\"SUCCESS\",\"code\":200}");
    }
}

項目啟動后茶行,請求http://localhost:8085/aop/getTest接口:

請求http://localhost:8085/aop/postTest接口,控制臺無輸出登钥,證明切點確實是只針對被GetMapping修飾的方法畔师。

2.2 第二個實例

下面我們將問題復雜化一些,該例的場景是:

  1. 自定義一個注解PermissionsAnnotation
  2. 創(chuàng)建一個切面類牧牢,切點設置為攔截所有標注PermissionsAnnotation的方法看锉,截取到接口的參數(shù),進行簡單的權限校驗
  3. PermissionsAnnotation標注在測試接口類的測試接口test

具體的實現(xiàn)步驟:

  1. 使用@Target塔鳍、@Retention伯铣、@Documented自定義一個注解:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PermissionAnnotation{
}
  1. 創(chuàng)建第一個AOP切面類,轮纫,只要在類上加個 @Aspect 注解即可腔寡。@Aspect 注解用來描述一個切面類,定義切面類的時候需要打上這個注解掌唾。@Component 注解將該類交給 Spring 來管理放前。在這個類里實現(xiàn)第一步權限校驗邏輯:
package com.example.demo;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Aspect
@Component
@Order(1)
public class PermissionFirstAdvice {

 // 定義一個切面,括號內(nèi)寫入第1步中自定義注解的路徑
    @Pointcut("@annotation(com.mu.demo.annotation.PermissionAnnotation)")
    private void permissionCheck() {
    }

    @Around("permissionCheck()")
    public Object permissionCheckFirst(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("===================第一個切面===================:" + System.currentTimeMillis());

        //獲取請求參數(shù)糯彬,詳見接口類
        Object[] objects = joinPoint.getArgs();
        Long id = ((JSONObject) objects[0]).getLong("id");
        String name = ((JSONObject) objects[0]).getString("name");
        System.out.println("id1->>>>>>>>>>>>>>>>>>>>>>" + id);
        System.out.println("name1->>>>>>>>>>>>>>>>>>>>>>" + name);

        // id小于0則拋出非法id的異常
        if (id < 0) {
            return JSON.parseObject("{\"message\":\"illegal id\",\"code\":403}");
        }
        return joinPoint.proceed();
    }
}
  1. 創(chuàng)建接口類凭语,并在目標方法上標注自定義注解 PermissionsAnnotation
package com.example.demo;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping(value = "/permission")
public class TestController {
    @RequestMapping(value = "/check", method = RequestMethod.POST)
    // 添加這個注解
    @PermissionsAnnotation()
    public JSONObject getGroupList(@RequestBody JSONObject request) {
        return JSON.parseObject("{\"message\":\"SUCCESS\",\"code\":200}");
    }
}

在這里,我們先進行一個測試撩扒。首先似扔,填好請求地址和header:

其次,構造正常的參數(shù):

可以拿到正常的響應結(jié)果:

然后,構造一個異常參數(shù)炒辉,再次請求:

響應結(jié)果顯示豪墅,切面類進行了判斷,并返回相應結(jié)果:

有人會問辆脸,如果我一個接口想設置多個切面類進行校驗怎么辦但校?這些切面的執(zhí)行順序如何管理?

很簡單啡氢,一個自定義的AOP注解可以對應多個切面類状囱,這些切面類執(zhí)行順序由@Order注解管理,該注解后的數(shù)字越小倘是,所在切面類越先執(zhí)行亭枷。

下面在實例中進行演示:

創(chuàng)建第二個AOP切面類,在這個類里實現(xiàn)第二步權限校驗:

package com.example.demo;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Aspect
@Component
@Order(0)
public class PermissionSecondAdvice {

   @Pointcut("@annotation(com.example.demo.PermissionsAnnotation)")
   private void permissionCheck() {
   }

   @Around("permissionCheck()")
   public Object permissionCheckSecond(ProceedingJoinPoint joinPoint) throws Throwable {
       System.out.println("===================第二個切面===================:" + System.currentTimeMillis());

       //獲取請求參數(shù)搀崭,詳見接口類
       Object[] objects = joinPoint.getArgs();
       Long id = ((JSONObject) objects[0]).getLong("id");
       String name = ((JSONObject) objects[0]).getString("name");
       System.out.println("id->>>>>>>>>>>>>>>>>>>>>>" + id);
       System.out.println("name->>>>>>>>>>>>>>>>>>>>>>" + name);

       // name不是管理員則拋出異常
       if (!name.equals("admin")) {
           return JSON.parseObject("{\"message\":\"not admin\",\"code\":403}");
       }
       return joinPoint.proceed();
   }
}

重啟項目叨粘,繼續(xù)測試,構造兩個參數(shù)都異常的情況:

響應結(jié)果瘤睹,表面第二個切面類執(zhí)行順序更靠前:

3 AOP相關注解

上面的案例中升敲,用到了諸多注解,下面針對這些注解進行詳解轰传。

3.1 @Pointcut

@Pointcut 注解驴党,用來定義一個切面,即上文中所關注的某件事情的入口获茬,切入點定義了事件觸發(fā)時機港庄。

@Aspect
@Component
public class LogAspectHandler {

    /**
     * 定義一個切面,攔截 com.itcodai.course09.controller 包和子包下的所有方法
     */
    @Pointcut("execution(* com.mutest.controller..*.*(..))")
    public void pointCut() {}
}

@Pointcut 注解指定一個切面恕曲,定義需要攔截的東西鹏氧,這里介紹兩個常用的表達式:一個是使用 execution(),另一個是使用 annotation()佩谣。

execution表達式:

execution(* * com.mutest.controller..*.*(..))) 表達式為例:

  • 第一個 * 號的位置:表示返回值類型把还,* 表示所有類型。
  • 包名:表示需要攔截的包名茸俭,后面的兩個句點表示當前包和當前包的所有子包吊履,在本例中指 com.mutest.controller包、子包下所有類的方法瓣履。
  • 第二個 * 號的位置:表示類名率翅,* 表示所有類练俐。
  • (..):這個星號表示方法名袖迎, 表示所有的方法,后面括弧里面表示方法的參數(shù),兩個句點表示任何參數(shù)燕锥。

annotation() 表達式:

annotation() 方式是針對某個注解來定義切面辜贵,比如我們對具有 @PostMapping 注解的方法做切面,可以如下定義切面:

@Pointcut("@annotation(org.springframework.web.bind.annotation.PostMapping)")
public void annotationPointcut() {}

然后使用該切面的話归形,就會切入注解是 @PostMapping 的所有方法托慨。這種方式很適合處理 @GetMapping、@PostMapping暇榴、@DeleteMapping不同注解有各種特定處理邏輯的場景厚棵。

還有就是如上面案例所示,針對自定義注解來定義切面蔼紧。

@Pointcut("@annotation(com.example.demo.PermissionsAnnotation)")
private void permissionCheck() {}

3.2 @Around

@Around注解用于修飾Around增強處理婆硬,Around增強處理非常強大,表現(xiàn)在:

  1. @Around可以自由選擇增強動作與目標方法的執(zhí)行順序奸例,也就是說可以在增強動作前后彬犯,甚至過程中執(zhí)行目標方法。這個特性的實現(xiàn)在于查吊,調(diào)用ProceedingJoinPoint參數(shù)的procedd()方法才會執(zhí)行目標方法谐区。
  2. @Around可以改變執(zhí)行目標方法的參數(shù)值,也可以改變執(zhí)行目標方法之后的返回值逻卖。

Around增強處理有以下特點:

  1. 當定義一個Around增強處理方法時宋列,該方法的第一個形參必須是 ProceedingJoinPoint 類型(至少一個形參)。在增強處理方法體內(nèi)箭阶,調(diào)用ProceedingJoinPointproceed方法才會執(zhí)行目標方法:這就是@Around增強處理可以完全控制目標方法執(zhí)行時機虚茶、如何執(zhí)行的關鍵;如果程序沒有調(diào)用ProceedingJoinPointproceed方法仇参,則目標方法不會執(zhí)行嘹叫。
  2. 調(diào)用ProceedingJoinPointproceed方法時,還可以傳入一個Object[ ]對象诈乒,該數(shù)組中的值將被傳入目標方法作為實參——這就是Around增強處理方法可以改變目標方法參數(shù)值的關鍵罩扇。這就是如果傳入的Object[ ]數(shù)組長度與目標方法所需要的參數(shù)個數(shù)不相等,或者Object[ ]數(shù)組元素與目標方法所需參數(shù)的類型不匹配怕磨,程序就會出現(xiàn)異常喂饥。

@Around功能雖然強大,但通常需要在線程安全的環(huán)境下使用肠鲫。因此员帮,如果使用普通的BeforeAfterReturning就能解決的問題导饲,就沒有必要使用Around了捞高。如果需要目標方法執(zhí)行之前和之后共享某種狀態(tài)數(shù)據(jù)氯材,則應該考慮使用Around。尤其是需要使用增強處理阻止目標的執(zhí)行硝岗,或需要改變目標方法的返回值時氢哮,則只能使用Around增強處理了。

下面型檀,在前面例子上做一些改造冗尤,來觀察@Around的特點。

自定義注解類不變胀溺。首先裂七,定義接口類:

package com.example.demo;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping(value = "/permission")
public class TestController {
    @RequestMapping(value = "/check", method = RequestMethod.POST)
    @PermissionsAnnotation()
    public JSONObject getGroupList(@RequestBody JSONObject request) {
        return JSON.parseObject("{\"message\":\"SUCCESS\",\"code\":200,\"data\":" + request + "}");
    }
}

唯一切面類(前面案例有兩個切面類,這里只需保留一個即可):

package com.example.demo;

import com.alibaba.fastjson.JSONObject;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Aspect
@Component
@Order(1)
public class PermissionAdvice {

    @Pointcut("@annotation(com.example.demo.PermissionsAnnotation)")
    private void permissionCheck() {
    }

    @Around("permissionCheck()")
    public Object permissionCheck(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("===================開始增強處理===================");

        //獲取請求參數(shù)仓坞,詳見接口類
        Object[] objects = joinPoint.getArgs();
        Long id = ((JSONObject) objects[0]).getLong("id");
        String name = ((JSONObject) objects[0]).getString("name");
        System.out.println("id1->>>>>>>>>>>>>>>>>>>>>>" + id);
        System.out.println("name1->>>>>>>>>>>>>>>>>>>>>>" + name);

  // 修改入?yún)?        JSONObject object = new JSONObject();
        object.put("id", 8);
        object.put("name", "lisi");
        objects[0] = object;

  // 將修改后的參數(shù)傳入
        return joinPoint.proceed(objects);
    }
}

同樣使用JMeter調(diào)用接口碍讯,傳入?yún)?shù):{"id":-5,"name":"admin"},響應結(jié)果表明:@Around截取到了接口的入?yún)⒊短桑⑹菇涌诜祷亓饲忻骖愔械慕Y(jié)果捉兴。

3.3 @Before

@Before 注解指定的方法在切面切入目標方法之前執(zhí)行,可以做一些 Log 處理录语,也可以做一些信息的統(tǒng)計倍啥,比如獲取用戶的請求 URL 以及用戶的 IP 地址等等,這個在做個人站點的時候都能用得到澎埠,都是常用的方法虽缕。例如下面代碼:

@Aspect
@Component
@Slf4j
public class LogAspectHandler {
    /**
     * 在上面定義的切面方法之前執(zhí)行該方法
     * @param joinPoint jointPoint
     */
    @Before("pointCut()")
    public void doBefore(JoinPoint joinPoint) {
        log.info("====doBefore方法進入了====");

        // 獲取簽名
        Signature signature = joinPoint.getSignature();
        // 獲取切入的包名
        String declaringTypeName = signature.getDeclaringTypeName();
        // 獲取即將執(zhí)行的方法名
        String funcName = signature.getName();
        log.info("即將執(zhí)行方法為: {},屬于{}包", funcName, declaringTypeName);

        // 也可以用來記錄一些信息蒲稳,比如獲取請求的 URL 和 IP
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        // 獲取請求 URL
        String url = request.getRequestURL().toString();
        // 獲取請求 IP
        String ip = request.getRemoteAddr();
        log.info("用戶請求的url為:{}氮趋,ip地址為:{}", url, ip);
    }
}

JointPoint 對象很有用,可以用它來獲取一個簽名江耀,利用簽名可以獲取請求的包名剩胁、方法名,包括參數(shù)(通過 joinPoint.getArgs() 獲认楣)等昵观。

搜索程序員麥冬公眾號,回復“888”舌稀,送你一份2020最新Java面試題手冊.pdf

3.4 @After

@After 注解和 @Before 注解相對應啊犬,指定的方法在切面切入目標方法之后執(zhí)行,也可以做一些完成某方法之后的 Log 處理壁查。

@Aspect
@Component
@Slf4j
public class LogAspectHandler {
    /**
     * 定義一個切面觉至,攔截 com.mutest.controller 包下的所有方法
     */
    @Pointcut("execution(* com.mutest.controller..*.*(..))")
    public void pointCut() {}

    /**
     * 在上面定義的切面方法之后執(zhí)行該方法
     * @param joinPoint jointPoint
     */
    @After("pointCut()")
    public void doAfter(JoinPoint joinPoint) {

        log.info("==== doAfter 方法進入了====");
        Signature signature = joinPoint.getSignature();
        String method = signature.getName();
        log.info("方法{}已經(jīng)執(zhí)行完", method);
    }
}

到這里,我們來寫個 Controller 測試一下執(zhí)行結(jié)果睡腿,新建一個 AopController 如下:

@RestController
@RequestMapping("/aop")
public class AopController {

    @GetMapping("/{name}")
    public String testAop(@PathVariable String name) {
        return "Hello " + name;
    }
}

啟動項目语御,在瀏覽器中輸入:localhost:8080/aop/csdn领斥,觀察一下控制臺的輸出信息:

====doBefore 方法進入了====  
即將執(zhí)行方法為: testAop,屬于com.itcodai.mutest.AopController包  
用戶請求的 url 為:http://localhost:8080/aop/name沃暗,ip地址為:0:0:0:0:0:0:0:1  
==== doAfter 方法進入了====  
方法 testAop 已經(jīng)執(zhí)行完

從打印出來的 Log 中可以看出程序執(zhí)行的邏輯與順序,可以很直觀的掌握 @Before@After兩個注解的實際作用何恶。

搜索程序員麥冬公眾號孽锥,回復“888”,送你一份2020最新Java面試題手冊.pdf

3.5 @AfterReturning

@AfterReturning 注解和 @After 有些類似细层,區(qū)別在于 @AfterReturning 注解可以用來捕獲切入方法執(zhí)行完之后的返回值惜辑,對返回值進行業(yè)務邏輯上的增強處理,例如:

@Aspect
@Component
@Slf4j
public class LogAspectHandler {
    /**
     * 在上面定義的切面方法返回后執(zhí)行該方法疫赎,可以捕獲返回對象或者對返回對象進行增強
     * @param joinPoint joinPoint
     * @param result result
     */
    @AfterReturning(pointcut = "pointCut()", returning = "result")
    public void doAfterReturning(JoinPoint joinPoint, Object result) {

        Signature signature = joinPoint.getSignature();
        String classMethod = signature.getName();
        log.info("方法{}執(zhí)行完畢盛撑,返回參數(shù)為:{}", classMethod, result);
        // 實際項目中可以根據(jù)業(yè)務做具體的返回值增強
        log.info("對返回參數(shù)進行業(yè)務上的增強:{}", result + "增強版");
    }
}

需要注意的是,在 @AfterReturning 注解 中捧搞,屬性 returning 的值必須要和參數(shù)保持一致抵卫,否則會檢測不到。該方法中的第二個入?yún)⒕褪潜磺蟹椒ǖ姆祷刂堤テ玻?doAfterReturning 方法中可以對返回值進行增強介粘,可以根據(jù)業(yè)務需要做相應的封裝。我們重啟一下服務晚树,再測試一下:

方法 testAop 執(zhí)行完畢姻采,返回參數(shù)為:Hello CSDN  
對返回參數(shù)進行業(yè)務上的增強:Hello CSDN 增強版

3.6 @AfterThrowing

當被切方法執(zhí)行過程中拋出異常時,會進入 @AfterThrowing 注解的方法中執(zhí)行爵憎,在該方法中可以做一些異常的處理邏輯慨亲。要注意的是 throwing 屬性的值必須要和參數(shù)一致,否則會報錯宝鼓。該方法中的第二個入?yún)⒓礊閽伋龅漠惓刑棵!?/p>

@Aspect
@Component
@Slf4j
public class LogAspectHandler {
    /**
     * 在上面定義的切面方法執(zhí)行拋異常時,執(zhí)行該方法
     * @param joinPoint jointPoint
     * @param ex ex
     */
    @AfterThrowing(pointcut = "pointCut()", throwing = "ex")
    public void afterThrowing(JoinPoint joinPoint, Throwable ex) {
        Signature signature = joinPoint.getSignature();
        String method = signature.getName();
        // 處理異常的邏輯
        log.info("執(zhí)行方法{}出錯愚铡,異常為:{}", method, ex);
    }
}

最后

感謝大家看到這里铐望,如果本文有什么不足之處,歡迎多多指教茂附;如果你覺得對你有幫助正蛙,請給我點個贊。

也歡迎大家關注我的公眾號:程序員麥冬营曼,每天更新行業(yè)資訊乒验!

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市蒂阱,隨后出現(xiàn)的幾起案子锻全,更是在濱河造成了極大的恐慌狂塘,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鳄厌,死亡現(xiàn)場離奇詭異荞胡,居然都是意外死亡,警方通過查閱死者的電腦和手機了嚎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門泪漂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人歪泳,你說我怎么就攤上這事萝勤。” “怎么了呐伞?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵敌卓,是天一觀的道長。 經(jīng)常有香客問我伶氢,道長趟径,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任癣防,我火速辦了婚禮舵抹,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘劣砍。我一直安慰自己惧蛹,他們只是感情好,可當我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布刑枝。 她就那樣靜靜地躺著香嗓,像睡著了一般。 火紅的嫁衣襯著肌膚如雪装畅。 梳的紋絲不亂的頭發(fā)上靠娱,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天,我揣著相機與錄音掠兄,去河邊找鬼像云。 笑死,一個胖子當著我的面吹牛蚂夕,可吹牛的內(nèi)容都是我干的迅诬。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼婿牍,長吁一口氣:“原來是場噩夢啊……” “哼侈贷!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起等脂,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤俏蛮,失蹤者是張志新(化名)和其女友劉穎撑蚌,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體搏屑,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡争涌,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了辣恋。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片亮垫。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖抑党,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情撵摆,我是刑警寧澤底靠,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站特铝,受9級特大地震影響暑中,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜鲫剿,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一鳄逾、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧灵莲,春花似錦雕凹、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至明场,卻和暖如春汽摹,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背苦锨。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工逼泣, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人舟舒。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓拉庶,卻偏偏與公主長得像,于是被迫代替她去往敵國和親秃励。 傳聞我的和親對象是個殘疾皇子砍的,可洞房花燭夜當晚...
    茶點故事閱讀 42,786評論 2 345