工作流引擎Activiti

一、Activiti 简介

Activiti 是一个基于 Java 的、轻量级的开源工作流和业务流程管理 (BPM, Business Process Management) 引擎。它最初由 Alfresco 公司发起,遵循 Apache 2.0 开源协议,并且符合 BPMN 2.0 标准。后来由于团队和商业化等原因,Activiti 项目分裂出了 Flowable 等分支版本,但核心理念与使用方式大同小异。

在各类 BPM 产品中,Activiti 因其轻量、易于与 Spring 等主流框架集成而备受青睐。它提供了一个基于 BPMN2.0 的建模与执行平台,便于对复杂的业务流程进行可视化设计、自动化执行与监控。


二、Activiti 相关定义与主要概念

下面是 Activiti(以及大多数 BPMN 流程引擎)中需要重点理解的几个概念:

  1. Process Definition(流程定义)

    • 指的是业务流程在 BPMN 中进行建模后的“图”或“流程模型”。该模型描述了业务处理的各个环节(用户任务、服务任务、网关、事件等),以及它们之间的流转逻辑。
    • 在实际运行时,会将“流程定义”部署到 Activiti 引擎中,并以相应版本进行管理。
  2. BPMN 2.0(Business Process Model and Notation)

    • BPMN 2.0 是业务流程建模的标准语言,它定义了一套图形化的符号(如网关、任务、边界事件等)来描述业务流程。
    • 通过 BPMN,可以直观地看到流程的执行顺序、分支条件和并行流程等。
  3. Process Instance(流程实例)

    • 流程定义是模型层面的抽象,而流程实例是该模型每一次真正运行的实体。例如“请假申请流程”是一个流程定义,小王的请假单在运行时就对应该流程定义的一次实例。
    • Activiti 根据流程定义来创建并运行流程实例,对应着真实业务数据与当前流程进展。
  4. Task(任务)

    • BPMN 中常见的“用户任务”、“服务任务”等就是“任务”的具体类型。Activiti 会在引擎中将这些任务进行调度或分配,并在流程流转中把任务指派给具体的人或系统处理。
    • “用户任务”需要由人工来完成,可以分配给某个用户或用户组;“服务任务”则是由系统自动执行,如调用一个外部接口或服务。
  5. Gateway(网关)

    • 流程分支或合并的控制元素。BPMN 中常见的网关有:排他网关 (Exclusive Gateway)、并行网关 (Parallel Gateway)、包容网关 (Inclusive Gateway) 等。
    • 通过网关,可以根据业务逻辑和条件控制流程的分支流转或并行分支。
  6. Event(事件)

    • 事件通常用来表示流程的开始、结束、中断或其他时机。常见的事件类型有:开始事件 (Start Event)、结束事件 (End Event)、边界事件 (Boundary Event) 等。
  7. 部署(Deployment)

    • 将 BPMN 模型(流程定义文件)导入到 Activiti 引擎并进行版本化管理的过程。部署后,Activiti 才能将该流程定义变成可执行的流程模型。
  8. 引擎接口与 API

    • Activiti 提供了丰富的 API 用于启动流程、查询任务、完成任务、跳转流程节点等常见操作。
    • 常见接口包括 RuntimeService, TaskService, RepositoryService, HistoryService 等。

三、典型应用场景

  1. 审批流程

    • 请假、报销、采购、用印等常见的OA审批流程。通常存在串行或并行审批环节,甚至需要会签、加签、撤销等功能。
    • 通过 Activiti 定义审批流程后,业务系统可以根据当前流程状态自动识别待办任务列表、审批节点、审批历史等。
  2. 订单处理、售后流程

    • 电商系统中,有关订单处理、退货、换货流程等。不同条件下会有不同的审批或自动化处理分支。
    • 通过 BPMN,能将业务场景可视化,更直观地对复杂分支进行维护。
  3. 多人协作、跨部门流程

    • 当业务需要多个角色或部门协同完成时,经常存在各种分支、条件、并行处理,对简单的状态机来说难以管理,采用工作流引擎可以灵活定义与扩展。
  4. 复杂的服务编排、系统间交互

    • 在企业级微服务架构中,需要多个服务之间有序或并行的处理工作,Activiti 可以作为“编排者”进行流程控制和状态跟踪。

四、为什么不用简单的 status 字段来处理,而要使用工作流引擎?

在许多业务场景下,若流程比较简单,确实可能只需要一个状态字段(如 Pending -> Approved -> Rejected -> Completed)就能满足需求。但当流程变复杂时,简单的状态机往往力不从心。主要原因包括:

  1. 状态种类多、流转逻辑复杂

    • 复杂业务中可能有多种分支、并行、条件校验、异常处理等场景,单纯的状态字段没法轻易表示这些逻辑,更别提动态修改或拓展流程了。
    • 例如一个审批流程里,用户可能需要临时加签、会签、撤回、跳转到前一步等需求,这都需要在简单的状态模型里做大量“手工维护”。
  2. 难以维护与扩展

    • 复杂流程一旦需要修改,往往要改代码中大量的 if-else 或 switch 分支逻辑,错误或遗漏的概率也会大大增加。
    • 而工作流引擎可以让流程逻辑从业务代码中解耦,通过 BPMN 流程图来建模并使用引擎执行,维护和改动相对更直观、更安全。
  3. 可视化与监控

    • 工作流引擎通常提供可视化的流程设计器以及监控工具。运营人员、业务人员可以直观了解流程到达了哪里、每个环节谁在处理、处理耗时等信息,并在必要时进行干预。
    • 如果只是用一个状态字段,通常缺乏对流程执行过程的可视化管理,需要自己额外开发大量记录与监控功能。
  4. 可审计性

    • 工作流引擎会自动记录流程节点的执行历史、任务处理人、处理时间、审批结果等,可以在事后对流程进行审计与溯源。
    • 简单的状态字段只记录了当下状态,而缺乏中间环节的信息,需要自己额外实现审计。
  5. 通用的流程设计与复用

    • 通过 BPMN 2.0 建立流程模型,可以复用相同或相似的流程,不需要在每个业务功能里重复开发。
    • 工作流引擎也能提供“子流程”等特性,用于流程间的组合和复用。

  • 工作流引擎 Activiti 提供了从流程建模、部署、执行到监控的完整解决方案,通过 BPMN 2.0 标准将复杂业务流程以图形化的形式抽象并可执行。
  • 对于业务流程相对固定且简单的场景,可以使用简单的状态机来处理,但当流程变得复杂,或者对可视化、审计、动态修改流程有更高要求时,工作流引擎能够更好地应对这些挑战。
  • 通过将流程逻辑从业务系统中剥离出来,Activiti 可以显著降低程序逻辑的复杂度,提高流程维护和扩展的效率,也有助于企业实现更全面、更精细的流程化管理。

Code Demo

  1. 项目结构
  2. 依赖与配置
  3. 示例流程(BPMN 文件)
  4. Java 代码示例
  5. 前置动作:数据库、部署流程
  6. 如何在前端查看流程图
  7. 如何运行示例

说明

  • Activiti 7 与之前的 Activiti 6.x 有一些变更,也可以选择 Flowable 或者其他 BPMN 引擎,原理都类似。

1. 项目结构

以下是一个示例的 Maven 项目结构:

activiti-demo
├── src
│   ├── main
│   │   ├── java
│   │   │   └── com.example.activiti
│   │   │       ├── ActivitiDemoApplication.java        # Spring Boot 启动类
│   │   │       ├── config
│   │   │       │   └── ActivitiConfig.java             # Activiti 配置
│   │   │       ├── controller
│   │   │       │   └── LeaveController.java            # 示例控制器
│   │   │       ├── listener
│   │   │       │   └── UserTaskListener.java           # 示例任务监听器
│   │   │       ├── service
│   │   │       │   └── LeaveService.java               # 自定义服务类
│   │   │       └── model
│   │   │           └── LeaveRequest.java               # 请假申请实体
│   │   ├── resources
│   │   │   ├── application.yml                         # Spring Boot 配置
│   │   │   ├── processes
│   │   │   │   └── leave-process.bpmn20.xml            # 流程定义文件 (BPMN 2.0)
│   │   │   └── static
│   │   │       └── diagram                             # 流程图输出目录 (可选)
│   │   └── webapp
│   │       └── index.html                              # 简单的前端页面(可选)
└── pom.xml


2. 依赖与配置

2.1 Maven 依赖

示例使用 Activiti 7 以及 Spring Boot,一般在 pom.xml 中引入(以最新版本为例,如有更新可自行调整):

<dependencies>
    <!-- Spring Boot Starter Web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- Spring Boot Starter JPA + H2 DB (内存数据库演示) -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <scope>runtime</scope>
    </dependency>

    <!-- Activiti 7 Starter -->
    <dependency>
        <groupId>org.activiti</groupId>
        <artifactId>activiti-spring-boot-starter</artifactId>
        <version>7.3.0</version>
    </dependency>

    <!-- Lombok (可选) -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>

</dependencies>

如果只想试验流程,可以使用内存数据库 H2,方便快速测试。生产环境或正式开发中,应配置 MySQL / PostgreSQL 等数据库,并确保数据库链接无误。

2.2 Spring Boot 配置

使用 application.yml 为例,内容大致如下:

server:
  port: 8080

spring:
  datasource:
    driver-class-name: org.h2.Driver
    url: jdbc:h2:mem:activiti-test;DB_CLOSE_ON_EXIT=FALSE;DB_CLOSE_DELAY=-1
    username: sa
    password:
  h2:
    console:
      enabled: true
      path: /h2-console

# 让 Spring Data JPA 自动建表
spring.jpa.hibernate.ddl-auto: update

# Activiti 自定义配置
activiti:
  # 这里 Activiti 7 有多种配置方式,比如对用户、组的管理等


3. 示例流程(BPMN 文件)

是个图,但本质还是个xml文件

假设我们有一个请假流程 (Leave Process),最简单的流程定义可能如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
             xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
             xmlns:dc="http://www.omg.org/spec/DD/20100524/DC"
             xmlns:di="http://www.omg.org/spec/DD/20100524/DI"
             xmlns:activiti="http://activiti.org/bpmn"
             targetNamespace="http://www.activiti.org/test">

    <process id="leaveProcess" name="Leave Process" isExecutable="true">
        <!-- 开始事件 -->
        <startEvent id="startEvent" name="Start">
            <outgoing>flow1</outgoing>
        </startEvent>

        <!-- 申请人提交申请(用户任务) -->
        <userTask id="applyTask" name="Apply for Leave" activiti:assignee="${applicant}">
            <incoming>flow1</incoming>
            <outgoing>flow2</outgoing>
        </userTask>

        <!-- 部门领导审批(用户任务) -->
        <userTask id="approveTask" name="Manager Approval" activiti:assignee="${manager}">
            <incoming>flow2</incoming>
            <outgoing>flow3</outgoing>
        </userTask>

        <!-- 排他网关: 同意 or 拒绝 -->
        <exclusiveGateway id="exclusiveGateway" name="Exclusive Gateway">
            <incoming>flow3</incoming>
            <outgoing>flow4</outgoing>
            <outgoing>flow5</outgoing>
        </exclusiveGateway>

        <!-- 结束事件: 同意 -->
        <endEvent id="endEventApproved" name="Approved">
            <incoming>flow4</incoming>
        </endEvent>

        <!-- 结束事件: 拒绝 -->
        <endEvent id="endEventRejected" name="Rejected">
            <incoming>flow5</incoming>
        </endEvent>

        <!-- 顺序流定义 -->
        <sequenceFlow id="flow1" sourceRef="startEvent" targetRef="applyTask" />
        <sequenceFlow id="flow2" sourceRef="applyTask" targetRef="approveTask" />
        <sequenceFlow id="flow3" sourceRef="approveTask" targetRef="exclusiveGateway" />
        <!-- 假设审批人直接在UserTask完成任务时带上变量 “approved”=true/false,用条件判断跳转 -->
        <sequenceFlow id="flow4" sourceRef="exclusiveGateway" targetRef="endEventApproved">
            <conditionExpression xsi:type="tFormalExpression"><![CDATA[${approved == true}]]></conditionExpression>
        </sequenceFlow>
        <sequenceFlow id="flow5" sourceRef="exclusiveGateway" targetRef="endEventRejected">
            <conditionExpression xsi:type="tFormalExpression"><![CDATA[${approved == false}]]></conditionExpression>
        </sequenceFlow>

    </process>
</definitions>

  • 申请人发起申请 (applyTask)
  • 部门领导进行审批 (approveTask)
  • 根据流程变量 approved 的值,决定走“同意”还是“拒绝”
  • 对应跳转到不同的结束事件

注意:以上使用 ${applicant}${manager} 等表达式表示动态分配。你也可以在 userTask 中写死某个 assignee 或者使用 candidateGroups 等方式分配任务。


4. Java 代码示例

4.1 Spring Boot 启动类

package com.example.activiti;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ActivitiDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(ActivitiDemoApplication.class, args);
    }

}

4.2 Activiti 配置类

package com.example.activiti.config;

import org.springframework.context.annotation.Configuration;

@Configuration
public class ActivitiConfig {
    // 一般可以在此处自定义 Bean,比如 RepositoryService, RuntimeService, TaskService, 
    // 或者引入自定义的 IdentityService 等等。
    // Activiti 7 的 starter 会自动进行大部分自动配置,这里可以根据需求进行额外配置
}

4.3 请假申请实体 (可选)

package com.example.activiti.model;

import lombok.Data;

@Data
public class LeaveRequest {
    private String applicant;       // 申请人
    private String reason;          // 请假原因
    private int days;              // 请假天数
}

在 Demo 中,这个实体只是用来演示业务数据的接收与传递,实际项目中可用数据库实体进行持久化。

4.4 Service 类:调用工作流引擎

package com.example.activiti.service;

import com.example.activiti.model.LeaveRequest;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.TaskService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.Map;

@Service
public class LeaveService {

    @Autowired
    private RuntimeService runtimeService;

    @Autowired
    private TaskService taskService;

    /**
     * 启动请假流程
     */
    public String startLeaveProcess(LeaveRequest leaveRequest) {
        // 将业务信息作为流程变量传递
        Map<String, Object> vars = new HashMap<>();
        vars.put("applicant", leaveRequest.getApplicant());
        // manager 可以根据不同申请人或业务规则决定
        vars.put("manager", "managerUser");  
        vars.put("approved", false);   // 初始默认未审批

        // 使用流程定义的 key
        String processDefinitionKey = "leaveProcess";
        // 启动流程实例
        // 这里返回的是流程实例 ID, 也可以存储到数据库中与业务关联
        return runtimeService.startProcessInstanceByKey(processDefinitionKey, vars).getId();
    }

    /**
     * 申请人提交申请(完成第一个UserTask)
     */
    public void submitLeaveApply(String taskId) {
        // 完成任务,下一步流转到领导审批节点
        taskService.complete(taskId);
    }

    /**
     * 领导审批
     */
    public void approve(String taskId, boolean approved) {
        Map<String, Object> vars = new HashMap<>();
        vars.put("approved", approved);
        taskService.complete(taskId, vars);
    }
}

4.5 Controller 示例

package com.example.activiti.controller;

import com.example.activiti.model.LeaveRequest;
import com.example.activiti.service.LeaveService;
import org.activiti.engine.TaskService;
import org.activiti.engine.task.Task;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/leave")
public class LeaveController {

    @Autowired
    private LeaveService leaveService;

    @Autowired
    private TaskService taskService;

    /**
     * 启动请假流程
     */
    @PostMapping("/start")
    public String startLeaveProcess(@RequestBody LeaveRequest leaveRequest) {
        String processInstanceId = leaveService.startLeaveProcess(leaveRequest);
        return "流程已启动,ID=" + processInstanceId;
    }

    /**
     * 获取当前待办任务(仅作演示:获取所有待办)
     */
    @GetMapping("/tasks")
    public List<Task> getTasks() {
        // 这里简单查询所有任务
        return taskService.createTaskQuery().list();
    }

    /**
     * 申请人提交申请
     */
    @PostMapping("/apply/{taskId}")
    public String submitApplication(@PathVariable("taskId") String taskId) {
        leaveService.submitLeaveApply(taskId);
        return "申请提交成功";
    }

    /**
     * 领导审批
     */
    @PostMapping("/approve/{taskId}")
    public String approveApplication(@PathVariable("taskId") String taskId,
                                     @RequestParam("approved") boolean approved) {
        leaveService.approve(taskId, approved);
        return "审批完成,结果=" + (approved ? "通过" : "拒绝");
    }
}


5. 前置动作:数据库、部署流程

5.1 Activiti 自动部署流程定义

  • 将 BPMN 文件(如 leave-process.bpmn20.xml)放置在 src/main/resources/processes/ 目录下,Activiti Starter 会自动扫描并部署。
  • 若需手动部署,可在代码中通过 RepositoryService 部署。自动部署可省去手动操作,非常方便。
/*
RepositoryService repositoryService;
Deployment deployment = repositoryService.createDeployment()
        .addClasspathResource("processes/leave-process.bpmn20.xml")
        .name("Leave Process Deployment")
        .deploy();
*/

5.2 运行数据库(H2 内存数据库)

  • 由于我们在 application.yml 中配置了 H2 内存数据库,启动 Spring Boot 时会自动初始化。
  • 如果想查看表结构,可以通过访问 http://localhost:8080/h2-console,JDBC URL 与用户名密码与 yml 一致。

6. 在前端 / 浏览器中查看流程图

6.1 动态生成流程图

Activiti 提供了一些方法,可以在流程部署后,读取流程定义并生成对应的 流程图(PNG / SVG),然后可供前端展示。

示例代码大致如下:

@GetMapping("/diagram/{processDefinitionId}")
public void generateProcessDiagram(@PathVariable("processDefinitionId") String processDefinitionId,
                                   HttpServletResponse response) throws IOException {
    BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);

    // 使用Activiti的ProcessDiagramGenerator生成图片
    ProcessEngineConfiguration processEngineConfiguration = ((RepositoryServiceImpl) repositoryService)
            .getProcessEngineConfiguration();

    ProcessDiagramGenerator diagramGenerator = processEngineConfiguration.getProcessDiagramGenerator();

    // 高亮当前节点或历史节点(可选)
    // 这里先不加高亮,直接画所有节点
    List<String> highLightedActivities = Collections.emptyList();
    List<String> highLightedFlows = Collections.emptyList();

    InputStream in = diagramGenerator.generateDiagram(
            bpmnModel,
            highLightedActivities,
            highLightedFlows,
            "宋体", "宋体", "宋体",
            processEngineConfiguration.getClassLoader(),
            1.0, true);

    // 写入响应
    response.setContentType("image/png");
    byte[] buffer = new byte[1024];
    int len;
    while ((len = in.read(buffer)) != -1) {
        response.getOutputStream().write(buffer, 0, len);
    }
    in.close();
}

  • 当我们调用 GET /diagram/{processDefinitionId} 时,后端会动态生成一张流程图 PNG 并输出。前端可将其直接显示。
  • 如果需要高亮流程中已经完成的任务节点或正在执行的节点,可以从 HistoryServiceRuntimeService 获取相关信息,然后传入 highLightedActivitieshighLightedFlows

6.2 BPMN 文件可视化

  • 有时也可以直接在前端上传 BPMN 文件到 Activiti Modeler 或其他 BPMN 在线编辑器查看。
  • 也可以保留 BPMN XML 文件下载供查看。

7. 如何运行示例

  1. 克隆或下载 此示例项目到本地。
  2. 确保安装了 JDK 8+、Maven 3+。
  3. 在项目根目录执行 mvn clean package
  4. 运行 ActivitiDemoApplication (IDE 中运行或命令行 mvn spring-boot:run)。
  5. 访问 http://localhost:8080,就可以开始进行接口测试。

测试步骤(模拟一个最简单的流程):

  1. 启动流程

    POST /leave/start
    Request Body:
    {
      "applicant": "Alice",
      "reason": "Take a vacation",
      "days": 3
    }
    
    

    响应:流程已启动,ID=xxxxx

  2. 查询待办任务

    GET /leave/tasks
    
    

    响应示例:

    [
      {
        "id": "2505",
        "name": "Apply for Leave",
        "assignee": "Alice",
        ...
      }
    ]
    
    
  3. 完成申请人提交 (完成第一个用户任务)

    POST /leave/apply/2505
    
    

    响应:申请提交成功

  4. 再次查询待办

    GET /leave/tasks
    
    

    响应示例:

    [
      {
        "id": "5005",
        "name": "Manager Approval",
        "assignee": "managerUser",
        ...
      }
    ]
    
    
  5. 领导审批 (完成第二个用户任务,传入审批结果)

    POST /leave/approve/5005?approved=true
    
    

    响应:审批完成,结果=通过

  6. 流程结束

    • 此时可在流程引擎的历史中查到该流程已经结束。
  7. 生成流程图(可选)

    • 在浏览器中访问:http://localhost:8080/diagram/leaveProcess:1:... (此处 ID 要换成实际查询到的流程定义 ID 或 instance ID)
    • 即可查看自动生成的流程图。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,816评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,729评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,300评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,780评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,890评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,084评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,151评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,912评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,355评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,666评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,809评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,504评论 4 334
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,150评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,882评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,121评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,628评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,724评论 2 351

推荐阅读更多精彩内容