第四十五章:基于SpringBoot 設計業務邏輯異常統一處理

在我們平時的項目研發過程中,異常一般都是程序員最為頭疼的問題,異常的拋出、捕獲、處理等既涉及事務回滾,還會涉及返回前端消息提醒信息。那么我們怎么設計可以解決上面的兩個的痛點呢?我們可不可以統一處理業務邏輯然后給出前端對應的異常提醒內容呢?

免費教程專題

恒宇少年在博客整理三套免費學習教程專題,由于文章偏多特意添加了閱讀指南,新文章以及之前的文章都會在專題內陸續填充,希望可以幫助大家解惑更多知識點。

本章目標

基于SpringBoot平臺構建業務邏輯異常統一處理,異常消息內容格式化。

SpringBoot 企業級核心技術學習專題


專題 專題名稱 專題描述
001 Spring Boot 核心技術 講解SpringBoot一些企業級層面的核心組件
002 Spring Boot 核心技術章節源碼 Spring Boot 核心技術簡書每一篇文章碼云對應源碼
003 Spring Cloud 核心技術 對Spring Cloud核心技術全面講解
004 Spring Cloud 核心技術章節源碼 Spring Cloud 核心技術簡書每一篇文章對應源碼
005 QueryDSL 核心技術 全面講解QueryDSL核心技術以及基于SpringBoot整合SpringDataJPA
006 SpringDataJPA 核心技術 全面講解SpringDataJPA核心技術
007 SpringBoot核心技術學習目錄 SpringBoot系統的學習目錄,敬請關注點贊??!!

構建項目

我們將邏輯異常核心處理部分提取出來作為單獨的jar供其他模塊引用,創建項目在parent項目pom.xml添加公共使用的依賴,配置內容如下所示:

<dependencies>
        <!--Lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!--測試模塊依賴-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--web依賴-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
</dependencies>

項目創建完成后除了.idea、imlpom.xml保留,其他的都刪除。

異常處理核心子模塊

我們創建一個名為springboot-core-exception的子模塊,在該模塊內自定義一個LogicException運行時異常類,繼承RuntimeException并重寫構造函數,代碼如下所示:

/**
 * 自定義業務邏輯異常類
 * ========================
 * Created with IntelliJ IDEA.
 * User:恒宇少年
 * Date:2018/1/7
 * Time:下午2:38
 * 碼云:http://git.oschina.net/jnyqy
 * ========================
 *
 * @author yuqiyu
 */
public class LogicException extends RuntimeException {

    /**
     * 日志對象
     */
    private Logger logger = LoggerFactory.getLogger(LogicException.class);

    /**
     * 錯誤消息內容
     */
    protected String errMsg;
    /**
     * 錯誤碼
     */
    protected String errCode;
    /**
     * 格式化錯誤碼時所需參數列表
     */
    protected String[] params;


    /**
     * 獲取錯誤消息內容
     * 根據errCode從redis內獲取未被格式化的錯誤消息內容
     * 并通過String.format()方法格式化錯誤消息以及參數
     *
     * @return
     */
    public String getErrMsg() {
        return errMsg;
    }

    /**
     * 獲取錯誤碼
     *
     * @return
     */
    public String getErrCode() {
        return errCode;
    }

    /**
     * 獲取異常參數列表
     *
     * @return
     */
    public String[] getParams() {
        return params;
    }

    /**
     * 構造函數設置錯誤碼以及錯誤參數列表
     *
     * @param errCode 錯誤碼
     * @param params  錯誤參數列表
     */
    public LogicException(String errCode, String... params) {
        this.errCode = errCode;
        this.params = params;
        //獲取格式化后的異常消息內容
        this.errMsg = ErrorMessageTools.getErrorMessage(errCode, params);
        //錯誤信息
        logger.error("系統遇到如下異常,異常碼:{}>>>異常信息:{}", errCode, errMsg);
    }
}

在重寫的構造函數內需要傳遞兩個參數errCode、params,其目的是為了初始化類內的全局變量。

  • errCode:該字段是對應的異常碼,我們在后續文章內容中創建一個存放異常錯誤碼的枚舉,而errCode就是枚舉對應的字符串的值。
  • params:這里是對應errCode字符串含義描述時所需要的參數列表。
  • errMsg:格式化后的業務邏輯異常消息描述,我們在構造函數內可以看到調用了ErrorMessageTools.getErrorMessage(errCode,params);,這個方法作用是通過異常碼在數據庫內獲取未格式化的異常描述,通過傳遞的參數進行格式化異常消息描述。

創建異常核心包的目的就是讓其他模塊直接添加依賴,那異常描述內容該怎么獲取呢?

定義異常消息獲取接口

我們在springboot-exception-core模塊內添加一個接口LogicExceptionMessage,該接口提供通過異常碼獲取未格式化的異常消息描述內容方法,接口定義如下所示:

/**
 * 邏輯異常接口定義
 * 使用項目需要實現該接口方法并提供方法實現
 * errCode對應邏輯異常碼
 * getMessage返回字符串為邏輯異常消息內容
 * ========================
 * Created with IntelliJ IDEA.
 * User:恒宇少年
 * Date:2018/1/7
 * Time:下午2:41
 * 碼云:http://git.oschina.net/jnyqy
 * ========================
 * @author yuqiyu
 */
public interface LogicExceptionMessage {

    /**
     * 獲取異常消息內容
     * @param errCode 錯誤碼
     * @return
     */
    public String getMessage(String errCode);
}

在需要加載springboot-exception-core依賴的項目中,創建實體類實現LogicExceptionMessage接口并重寫getMessage(String errCode)方法我們就可以通過spring IOC獲取實現類實例進行操作獲取數據,下面我們在編寫使用異常模塊時會涉及到。

格式化異常消息工具類

下面我們再回頭看看構造函數格式化異常消息工具類ErrorMessageTools,該工具類內提供getErrorMessage方法用于獲取格式化后的異常消息描述,代碼實現如下所示:

/**
 * 異常消息描述格式化工具類
 * ========================
 * Created with IntelliJ IDEA.
 * User:恒宇少年
 * Date:2018/1/7
 * Time:下午2:40
 * 碼云:http://git.oschina.net/jnyqy
 * ========================
 *
 * @author yuqiyu
 */
public class ErrorMessageTools {
    /**
     * 異常消息獲取
     *
     * @param errCode 異常消息碼
     * @param params  格式化異常參數所需參數列表
     * @return
     */
    public static String getErrorMessage(String errCode, Object... params) {
        //獲取業務邏輯消息實現
        LogicExceptionMessage logicExceptionMessage = SpringBeanTools.getBean(LogicExceptionMessage.class);
        if (ObjectUtils.isEmpty(logicExceptionMessage)) {
            try {
                throw new Exception("請配置實現LogicExceptionMessage接口并設置實現類被SpringIoc所管理。");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        //獲取錯誤消息內容
        String errMsg = logicExceptionMessage.getMessage(errCode);
        //格式化錯誤消息內容
        return ObjectUtils.isEmpty(params) ? errMsg : String.format(errMsg, params);
    }
}

注意:由于我們的工具類都是靜態方法調用方式,所以無法直接使用Spring IOC注解注入的方式獲取LogicExceptionMessage實例。

由于無法注入實例,在getErrorMessage方法內,我們通過工具類SpringBeanTools來獲取ApplicationContext上下文實例,再通過上下文來獲取指定類型的Bean;獲取到LogicExceptionMessage實例后調用getMessage方法,根據傳入的errCode就可以直接從接口實現類實例中獲取到未格式化的異常描述!

當然實現類可以是以Redis、Map集合數據庫、文本作為數據來源。

獲取到未格式化的異常描述后通過String.format方法以及傳遞的參數直接就可以獲取格式化后的字符串,如:

未格式化異常消息 => 用戶:%s已被凍結,無法操作.
格式化代碼 => String.format("%s已被凍結,無法操作.","恒宇少年");
格式化后效果 => 用戶:恒宇少年已被凍結,無法操作.

具體的格式化特殊字符含義可以去查看String.format文檔,如何獲取ApplicationContext上下文對象,請訪問第三十二章:如何獲取SpringBoot項目的applicationContext對象查看。

我們再回到LogicException構造函數內,這時errMsg字段對應的值就會是格式化后的異常消息描述,在外部我們調用getErrMsg方法就可以直接得到異常描述。

到目前為止,我們已經將springboot-exception-core模塊代碼編碼完成,下面我們來看下怎么來使用我們自定義的業務邏輯異常并且獲取格式化后的異常消息描述。

異常示例模塊

基于parent我們來創建一個名為springboot-exception-example的子模塊項目,項目內需要添加一些額外的配置依賴,當然也需要將我們的springboot-exception-core依賴添加進入,pom.xml配置文件內容如下所示:

<dependencies>
        <!--異常核心依賴-->
        <dependency>
            <groupId>com.hengyu</groupId>
            <artifactId>springboot-exception-core</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
        <!--spring data jpa依賴-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <!--數據庫驅動-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <!--druid依賴-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.6</version>
        </dependency>
</dependencies>

下面我們來配置下我們示例項目application.yml文件需要的配置,如下所示:

spring:
  application:
    name: springboot-exception-core
    #數據源配置
  datasource:
    druid:
      url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true
      username: root
      password: 123456
      driver-class-name: com.mysql.jdbc.Driver
  jpa:
    properties:
      hibernate:
        #配置顯示sql
        show_sql: true
        #配置格式化sql
        format_sql: true

在上面我們有講到LogicExceptionMessage獲取的內容可以從很多種數據源中讀取,我們還是采用數據庫來進行讀取,建議正式環境放到redis緩存內?。?!

異常信息表

接下來在數據庫內創建異常信息表sys_exception_info,語句如下:

DROP TABLE IF EXISTS `sys_exception_info`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `sys_exception_info` (
  `EI_ID` int(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵自增',
  `EI_CODE` varchar(30) DEFAULT NULL COMMENT '異常碼',
  `EI_MESSAGE` varchar(50) DEFAULT NULL COMMENT '異常消息內容',
  PRIMARY KEY (`EI_ID`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COMMENT='系統異?;拘畔?;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Dumping data for table `sys_exception_info`
--

LOCK TABLES `sys_exception_info` WRITE;
/*!40000 ALTER TABLE `sys_exception_info` DISABLE KEYS */;
INSERT INTO `sys_exception_info` VALUES (1,'USER_NOT_FOUND','用戶不存在.'),(2,'USER_STATUS_FAILD','用戶狀態異常.');
/*!40000 ALTER TABLE `sys_exception_info` ENABLE KEYS */;
UNLOCK TABLES;

我們通過spring-data-jpa來實現數據讀取,下面對應數據表創建對應的Entity

異常信息實體

/**
 * 系統異?;拘畔嶓w
 * ========================
 * Created with IntelliJ IDEA.
 * User:恒宇少年
 * Date:2018/1/7
 * Time:下午3:35
 * 碼云:http://git.oschina.net/jnyqy
 * ========================
 * @author yuqiyu
 */
@Data
@Entity
@Table(name = "sys_exception_info")
public class ExceptionInfoEntity implements Serializable{
    /**
     * 異常消息編號
     */
    @Id
    @GeneratedValue
    @Column(name = "EI_ID")
    private Integer id;
    /**
     * 異常消息錯誤碼
     */
    @Column(name = "EI_CODE")
    private String code;
    /**
     * 異常消息內容
     */
    @Column(name = "EI_MESSAGE")
    private String message;
}

異常信息數據接口

/**
 * 異常數據接口定義
 * ========================
 * Created with IntelliJ IDEA.
 * User:恒宇少年
 * Date:2018/1/7
 * Time:下午3:34
 * 碼云:http://git.oschina.net/jnyqy
 * ========================
 * @author yuqiyu
 */
public interface ExceptionRepository
    extends JpaRepository<ExceptionInfoEntity,Integer>
{
    /**
     * 根據異常碼獲取異常配置信息
     * @param code 異常碼
     * @return
     */
    ExceptionInfoEntity findTopByCode(String code);
}

在數據接口內通過spring-data-jpa方法查詢方式,通過errCode讀取異常信息實體內容。

在開發過程中異常跑出時所用到的errCode一般存放在枚舉類型或者常量接口內,在這里我們選擇可擴展相對來說比較強的枚舉類型,代碼如下:

/**
 * 錯誤碼枚舉類型
 * ========================
 * Created with IntelliJ IDEA.
 * User:恒宇少年
 * Date:2018/1/7
 * Time:下午3:25
 * 碼云:http://git.oschina.net/jnyqy
 * ========================
 * @author yuqiyu
 */
public enum ErrorCodeEnum {
    /**
     * 用戶不存在.
     */
    USER_NOT_FOUND,
    /**
     * 用戶狀態異常.
     */
    USER_STATUS_FAILD,
    //...添加其他錯誤碼
}

異常碼枚舉內容項是需要根據數據庫異常信息表對應變動的,能夠保證我們在拋出異常時,在數據庫內有對應的信息。

LogicExceptionMessage實現類定義

我們在springboot-exception-core核心模塊內添加了LogicExceptionMessage接口定義,需要我們實現該接口的getMessage方法核心模塊,這樣才可以獲取數據庫內對應的異常信息,實現類如下所示:

/**
 * 業務邏輯異常消息獲取實現類
 * - 消息可以從數據庫內獲取
 * - 消息可從Redis內獲取
 * ========================
 * Created with IntelliJ IDEA.
 * User:恒宇少年
 * Date:2018/1/7
 * Time:下午3:16
 * 碼云:http://git.oschina.net/jnyqy
 * ========================
 * @author yuqiyu
 */
@Component
public class LogicExceptionMessageSupport implements LogicExceptionMessage {

    /**
     * 異常數據接口
     */
    @Autowired
    private ExceptionRepository exceptionRepository;

    /**
     * 根據錯誤碼獲取錯誤信息
     * @param errCode 錯誤碼
     * @return
     */
    @Override
    public String getMessage(String errCode) {
        ExceptionInfoEntity exceptionInfoEntity = exceptionRepository.findTopByCode(errCode);
        if(!ObjectUtils.isEmpty(exceptionInfoEntity)) {
            return exceptionInfoEntity.getMessage();
        }
        return "系統異常";
    }
}

getMessage方法內通過ExceptionRepository數據接口定義的findTopByCode方法獲取指定異常嗎的異常信息,當存在異常信息時返回未格式化的異常描述。

統一返回實體定義

對于接口項目(包括前后分離項目)在處理返回統一格式時,我們通常會采用固定實體的方式,這樣對于前端調用接口的開發者來說解析內容是比較方便的,同樣在開發過程中會約定遇到系統異常、業務邏輯異常時返回的格式內容,當然這跟請求接口正確返回的格式是一樣的,只不過字段內容有差異。
統一返回實體ApiResponseEntity<T extends Object>如下:

/**
 * 接口響應實體
 * ========================
 * Created with IntelliJ IDEA.
 * User:恒宇少年
 * Date:2018/1/9
 * Time:下午3:04
 * 碼云:http://git.oschina.net/jnyqy
 * ========================
 * @author yuqiyu
 */
@Data
@Builder
public class ApiResponseEntity<T extends Object> {
    /**
     * 錯誤消息
     */
    private String errorMsg;
    /**
     * 數據內容
     */
    private T data;
}

ApiResponseEntity實體內,采用了Lombok的構造者設計模式@Builder注解,配置該注解的實體會自動在.class文件內添加內部類實現設計模式,部分自動生成代碼如下:

// ...
public static class ApiResponseEntityBuilder<T> {
        private String errorMsg;
        private T data;

        ApiResponseEntityBuilder() {
        }

        public ApiResponseEntity.ApiResponseEntityBuilder<T> errorMsg(String errorMsg) {
            this.errorMsg = errorMsg;
            return this;
        }

        public ApiResponseEntity.ApiResponseEntityBuilder<T> data(T data) {
            this.data = data;
            return this;
        }

        public ApiResponseEntity<T> build() {
            return new ApiResponseEntity(this.errorMsg, this.data);
        }

        public String toString() {
            return "ApiResponseEntity.ApiResponseEntityBuilder(errorMsg=" + this.errorMsg + ", data=" + this.data + ")";
        }
    }
// ...

到目前為止,我們并未添加全局異常相關的配置,而全局異常配置這塊,我們采用之前章節講到的@ControllerAdvice來實現,@ControllerAdvice相關的內容請訪問第二十一章:SpringBoot項目中的全局異常處理。

全局異常通知定義

我們本章節僅僅添加業務邏輯異常的處理,具體編碼如下所示:

/**
 * 控制器異常通知類
 * ========================
 * Created with IntelliJ IDEA.
 * User:恒宇少年
 * Date:2018/1/7
 * Time:下午5:30
 * 碼云:http://git.oschina.net/jnyqy
 * ========================
 *
 * @author yuqiyu
 */
@ControllerAdvice(annotations = RestController.class)
@ResponseBody
public class ExceptionAdvice {

    /**
     * logback new instance
     */
    Logger logger = LoggerFactory.getLogger(this.getClass());

    /**
     * 處理業務邏輯異常
     *
     * @param e 業務邏輯異常對象實例
     * @return 邏輯異常消息內容
     */
    @ExceptionHandler(LogicException.class)
    @ResponseStatus(code = HttpStatus.OK)
    public ApiResponseEntity<String> logicException(LogicException e) {
        logger.error("遇到業務邏輯異常:【{}】", e.getErrCode());
        // 返回響應實體內容
        return ApiResponseEntity.<String>builder().errorMsg(e.getErrMsg()).build();
    }
}

最近技術群內有同學問我,既然我們用的是@RestController為什么這里還需要配置@ResponseBody?這里給大家一個解釋,我們控制器通知確實是監聽的@RestController,而@RestController注解的控制器統一都是返回JSON格式的數據。那么我們在遇到異常后,請求已經不再控制器內了,已經交付給控制器通知類,那么我們通知類如果同樣想返回JSON數據,這里就需要配置@ResponseBody注解來實現。

我們來看上面logicException()方法,該方法返回值是我們定義的統一返回實體,目的是為了遇到業務邏輯異常時同樣返回與正確請求一樣的格式。

  • @ ExceptionHandler配置了將要處理LogicException類型的異常,也就是只要系統遇到LogicException異常并且拋給了控制器,就會調用該方法。
  • @ResponseStatus配置了返回的狀態值,因為我們遇到業務邏輯異常前端肯定需要的不是500錯誤,而是一個200狀態的JSON業務異常描述。

在方法返回時使用構造者設計模式并將異常消息傳遞給errorMsg()方法,這樣就實現了字段errorMsg的賦值。

測試

異常相關的編碼完成,下面我們來創建一個測試的控制器模擬業務邏輯發生時,系統是怎么做出的返回?
測試控制內容如下所示:

/**
 * 測試控制器
 * ========================
 * Created with IntelliJ IDEA.
 * User:恒宇少年
 * Date:2018/1/7
 * Time:下午3:12
 * 碼云:http://git.oschina.net/jnyqy
 * ========================
 *
 * @author yuqiyu
 */
@RestController
public class IndexController {
    /**
     * 首頁方法
     *
     * @return
     */
    @RequestMapping(value = "/index")
    public ApiResponseEntity<String> index() throws LogicException {
        /**
         * 模擬用戶不存在
         * 拋出業務邏輯異常
         */
        if (true) {
            throw new LogicException(ErrorCodeEnum.USER_STATUS_FAILD.toString());
        }
        return ApiResponseEntity.<String>builder().data("this is index mapping").build();
    }
}

根據上面代碼含義,當我們在訪問/index時就會發生USER_STATUS_FAILD業務邏輯異常,按照我們之前的全局異常配置以及統一返回實體實例化,訪問后會出現ApiResponseEntity格式JSON數據,下面我們運行項目訪問查看效果。
界面輸出內容如下所示:

{
    "errorMsg": "用戶狀態異常.",
    "data": null
}

而在控制臺由于我們編寫了日志信息,也同樣有對應的輸出,如下所示:

Hibernate: 
    select
        exceptioni0_.ei_id as ei_id1_0_,
        exceptioni0_.ei_code as ei_code2_0_,
        exceptioni0_.ei_message as ei_messa3_0_ 
    from
        sys_exception_info exceptioni0_ 
    where
        exceptioni0_.ei_code=? limit ?
2018-01-09 18:54:00.647 ERROR 2024 --- [nio-8080-exec-1] c.h.s.exception.core.LogicException      : 系統遇到如下異常,異常碼:USER_STATUS_FAILD>>>異常信息:用戶狀態異常.
2018-01-09 18:54:00.649 ERROR 2024 --- [nio-8080-exec-1] c.h.s.e.c.advice.ExceptionAdvice         : 遇到業務邏輯異常:【USER_STATUS_FAILD】

如果業務邏輯異常在Service層時,我們根本不需要去操心事務回滾的問題,因為LogicException本身就是運行時異常,而項目中拋出運行時異常時事務就會自動回滾。

我們把業務邏輯異常屏蔽掉,把true改成false查看正確時返回的格式,如下所示:

{
    "errorMsg": null,
    "data": "this is index mapping"
}

如果想把對應的null改成空字符串,請訪問查看第五章:配置使用FastJson返回Json視圖。

總結

本章將之前章節的部分內容進行了整合,主要是全局異常、統一格式返回等;這種方式是目前我們公司產品中正在使用的方式,已經可以滿足平時的業務邏輯異常定義以及返回,將異常消息存放到數據庫中我們可以隨時更新提示內容,這一點還是比較易用的。

本章源碼已經上傳到碼云:
SpringBoot配套源碼地址:https://gitee.com/hengboy/spring-boot-chapter
SpringCloud配套源碼地址:https://gitee.com/hengboy/spring-cloud-chapter

作者個人 博客
使用開源框架 ApiBoot 助你成為Api接口服務架構師

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,197評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,415評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,104評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,884評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,647評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,130評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,208評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,366評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,887評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,737評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,939評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,478評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,174評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,586評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,827評論 1 283
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,608評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,914評論 2 372

推薦閱讀更多精彩內容