Flowable开发--Modeler集成(六)

一、Flowable-Modeler功能

https://gitee.com/lwj/flow-modeler-sduty/tree/master/src/main

提供可视化编辑器,编辑BPMN流程,编辑CASE模型,编辑Form表单,编辑App应用,编辑决策表
提供可视化参数配置:每个流程可以配置详细的参数设置,按照流程对应的规范来设计。
提供导入导出功能:方便将流程结果导入到其他应用程序
在我们实际项目中,我们的流程配置和表单都是在一个系统中操作的,不可能在flowable的war包上做流程配置。
所以集成modeler是flowable使用的开端。

二、源码下载与编译

  1. 源码下载
    下载地址:
    https://github.com/flowable/flowable-engine

    源码

  2. 导入idea编译


    导入idea

    编译

    编译

编译结果:

[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary for Flowable 6.5.0-SNAPSHOT:
[INFO] 
[INFO] Flowable ........................................... SUCCESS [  0.326 s]
[INFO] Flowable - BPMN Model .............................. SUCCESS [  0.767 s]
[INFO] Flowable - Process Validation ...................... SUCCESS [  0.070 s]
[INFO] Flowable - BPMN Layout ............................. SUCCESS [  0.106 s]
[INFO] Flowable - Image Generator ......................... SUCCESS [  0.102 s]
[INFO] Flowable - Engine Common API ....................... SUCCESS [  0.059 s]
[INFO] Flowable - Variable Service API .................... SUCCESS [  0.036 s]
[INFO] Flowable - Engine Common ........................... SUCCESS [  0.455 s]
[INFO] Flowable - BPMN Converter .......................... SUCCESS [  0.143 s]
[INFO] Flowable - Entity Link Service API ................. SUCCESS [  0.021 s]
[INFO] Flowable - Entity Link Service ..................... SUCCESS [  0.163 s]
[INFO] Flowable - Variable Service ........................ SUCCESS [  0.198 s]
[INFO] Flowable - Identity Link Service API ............... SUCCESS [  0.028 s]
[INFO] Flowable - Identity Link Service ................... SUCCESS [  0.118 s]
[INFO] Flowable - Event Subscription Service API .......... SUCCESS [  0.028 s]
[INFO] Flowable - Event Subscription Service .............. SUCCESS [  0.127 s]
[INFO] Flowable - Task Service API ........................ SUCCESS [  0.030 s]
[INFO] Flowable - IDM API ................................. SUCCESS [  0.023 s]
[INFO] Flowable - Task Service ............................ SUCCESS [  0.169 s]
[INFO] Flowable - Job Service API ......................... SUCCESS [  0.023 s]
[INFO] Flowable - Job Service ............................. SUCCESS [  0.179 s]
[INFO] Flowable Job Spring Service ........................ SUCCESS [  0.141 s]
[INFO] Flowable - Batch Service API ....................... SUCCESS [  0.017 s]
[INFO] Flowable - Batch Service ........................... SUCCESS [  0.124 s]
[INFO] Flowable - IDM Engine .............................. SUCCESS [  0.229 s]
[INFO] flowable-idm-engine-configurator ................... SUCCESS [  0.108 s]
[INFO] Flowable - Form API ................................ SUCCESS [  0.023 s]
[INFO] Flowable - Form Model .............................. SUCCESS [  0.024 s]
[INFO] flowable-form-json-converter ....................... SUCCESS [  0.037 s]
[INFO] Flowable - Form Engine ............................. SUCCESS [  0.211 s]
[INFO] Flowable - CMMN Model .............................. SUCCESS [  0.062 s]
[INFO] Flowable - DMN Model ............................... SUCCESS [  0.024 s]
[INFO] Flowable - DMN API ................................. SUCCESS [  0.058 s]
[INFO] Flowable - CMMN API ................................ SUCCESS [  0.118 s]
[INFO] Flowable - Content API ............................. SUCCESS [  0.041 s]
[INFO] Flowable - Engine .................................. SUCCESS [  1.188 s]
[INFO] Flowable - Form Engine Configurator ................ SUCCESS [  0.384 s]
[INFO] Flowable - CMMN Converter .......................... SUCCESS [  0.060 s]
[INFO] Flowable - CMMN Image Generator .................... SUCCESS [  0.041 s]
[INFO] Flowable - CMMN Engine ............................. SUCCESS [  0.415 s]
[INFO] Flowable - CMMN Engine Configurator ................ SUCCESS [  0.477 s]
[INFO] Flowable - App Engine API .......................... SUCCESS [  0.020 s]
[INFO] Flowable - App Engine .............................. SUCCESS [  0.168 s]
[INFO] flowable-spring-security ........................... SUCCESS [  0.045 s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  9.645 s
[INFO] Finished at: 2019-10-20T23:21:14+08:00
[INFO] ------------------------------------------------------------------------

三、Modeler集成

1. 拷贝全都静态资源到项目中resources

源码文件

2. Maven依赖

由于flowable-modeler的流程设计器页面很多操作会访问后台接口,所以在这里导入依赖文件。

<!--web依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.hibernate.validator</groupId>
            <artifactId>hibernate-validator</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<!--Springboot-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<!--log4j-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<!--健康检查-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--数据库操作依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
</dependency>
<!--mybatis-plus 插件-->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.1.2</version>
</dependency>
<!--工作流-->
<dependency>
    <groupId>org.flowable</groupId>
    <artifactId>flowable-ui-common</artifactId>
    <version>6.4.2</version>
</dependency>
<dependency>
    <groupId>org.flowable</groupId>
    <artifactId>flowable-ui-modeler-conf</artifactId>
    <version>6.4.2</version>
</dependency>
<dependency>
    <groupId>org.flowable</groupId>
    <artifactId>flowable-ui-modeler-rest</artifactId>
    <version>6.4.2</version>
</dependency>
<dependency>
    <groupId>org.flowable</groupId>
    <artifactId>flowable-ui-modeler-logic</artifactId>
    <version>6.4.2</version>
</dependency>
<!--单元测试-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    <exclusions>
        <exclusion>
            <groupId>org.junit.vintage</groupId>
            <artifactId>junit-vintage-engine</artifactId>
        </exclusion>
    </exclusions>
</dependency>

3. 配置文件

配置文件:application.properties

server.port=8002
server.servlet.context-path=/flowable-modeler
management.endpoints.jmx.unique-names=true
# 这是强制使用JDK代理而不是使用CGLIB所必需的。
spring.aop.proxy-target-class=false
spring.aop.auto=false
spring.application.name=flowable-ui-modeler

# 安全配置
spring.security.filter.dispatcher-types=REQUEST,FORWARD,ASYNC
spring.liquibase.enabled=false
# 必须指定用于生成对象名的默认域。否则,当多个spring引导应用程序在同一个servlet容器中启动时
# 所有这些都将使用相同的名称创建(例如com.zaxxer.hikari:name=datasource,type=hikaridatasource)
spring.jmx.default-domain=${spring.application.name}

# 健康检查
# 将所有执行器端点暴露在Web上它们是公开的,但只有经过身份验证的用户才能看到/info和/health
# abd具有access admin的用户才能看到其他用户
management.endpoints.web.exposure.include=*
# 只有在授权用户时才应显示完整的运行状况详细信息
management.endpoint.health.show-details=when_authorized
# 只有具有角色access admin的用户才能访问完整的运行状况详细信息
management.endpoint.health.roles=access-admin

# 数据库 默认H2数据库
spring.datasource.username=flowable
spring.datasource.password=flowable

# 数据库连接池
spring.datasource.hikari.maxLifetime=600000
# 5 minutes
spring.datasource.hikari.idleTimeout=300000
spring.datasource.hikari.minimumIdle=5
spring.datasource.hikari.maximumPoolSize=50

# 大文件上传限制。设置为-1可设置为“无限制”。以字节表示
spring.servlet.multipart.max-file-size=10MB

配置文件:flowable-default.properties


# spring在角色前面加上role。然而,flowable还没有这个概念,所以我们需要用空字符串覆盖它。
flowable.common.app.role-prefix=

flowable.common.app.idm-url=http://localhost:8002/flowable-idm
flowable.common.app.idm-admin.user=admin
flowable.common.app.idm-admin.password=test

flowable.modeler.app.deployment-api-url=http://localhost:8002/flowable-task/app-api

# Rest API
flowable.modeler.app.rest-enabled=true
flowable.rest.app.authentication-mode=verify-privilege

配置文件:version.properties

type=modeler
version.major=6
version.minor=4
version.revision=2
version.edition=Flowable

4. 创建流程模型包

创建包:org.flowable.ui
在java中创建包名:org.flowable.ui

创建包

5. 去除认证

1)创建org.flowable.ui.common.rest.idm.remote包,添加类:

import org.flowable.ui.common.model.UserRepresentation;
import org.flowable.ui.common.security.DefaultPrivileges;
import org.flowable.ui.common.service.exception.NotFoundException;
import org.flowable.ui.common.service.idm.RemoteIdmService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList;
import java.util.List;

@RestController
@RequestMapping("/app")
public class RemoteAccountResource {

    @Autowired
    private RemoteIdmService remoteIdmService;

    /**
     * GET /rest/account -> get the current user.
     */
    @RequestMapping(value = "/rest/account", method = RequestMethod.GET, produces = "application/json")
    public UserRepresentation getAccount() {
//        UserRepresentation userRepresentation = null;
//        String currentUserId = SecurityUtils.getCurrentUserId();
//        if (currentUserId != null) {
//            RemoteUser remoteUser = remoteIdmService.getUser(currentUserId);
//            if (remoteUser != null) {
//                userRepresentation = new UserRepresentation(remoteUser);
//
//                if (remoteUser.getGroups() != null && remoteUser.getGroups().size() > 0) {
//                    List<GroupRepresentation> groups = new ArrayList<>();
//                    for (RemoteGroup remoteGroup : remoteUser.getGroups()) {
//                        groups.add(new GroupRepresentation(remoteGroup));
//                    }
//                    userRepresentation.setGroups(groups);
//                }
//
//                if (remoteUser.getPrivileges() != null && remoteUser.getPrivileges().size() > 0) {
//                    userRepresentation.setPrivileges(remoteUser.getPrivileges());
//                }
//
//            }
//        }
        UserRepresentation userRepresentation = new UserRepresentation();
        userRepresentation.setFirstName("admin");
        userRepresentation.setLastName("admin");
        userRepresentation.setFullName("admin");
        userRepresentation.setId("admin");
        List<String> pris = new ArrayList<>();
        pris.add(DefaultPrivileges.ACCESS_MODELER);
        pris.add(DefaultPrivileges.ACCESS_IDM);
        pris.add(DefaultPrivileges.ACCESS_ADMIN);
        pris.add(DefaultPrivileges.ACCESS_TASK);
        pris.add(DefaultPrivileges.ACCESS_REST_API);
        userRepresentation.setPrivileges(pris);

        if (userRepresentation != null) {
            return userRepresentation;
        } else {
            throw new NotFoundException();
        }
    }
}

2)创建包:org.flowable.ui.common.security 包添加以下类:

import org.flowable.idm.api.User;
import org.flowable.ui.common.model.RemoteUser;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;

import java.util.ArrayList;
import java.util.List;

public class SecurityUtils {

    private static User assumeUser;

    private SecurityUtils() {
    }

    /**
     * Get the login of the current user.
     */
    public static String getCurrentUserId() {
        User user = getCurrentUserObject();
        if (user != null) {
            return user.getId();
        }
        return null;
    }

    /**
     * @return the {@link User} object associated with the current logged in user.
     */
    public static User getCurrentUserObject() {
        if (assumeUser != null) {
            return assumeUser;
        }

//        User user = null;
//        FlowableAppUser appUser = getCurrentFlowableAppUser();
//        if (appUser != null) {
//            user = appUser.getUserObject();
//        }

        RemoteUser user = new RemoteUser();
//        FlowableAppUser appUser = getCurrentFlowableAppUser();
//        if (appUser != null) {
//            user = appUser.getUserObject();
//        }
        user.setId("admin");
        user.setDisplayName("admin");
        user.setFirstName("admin");
        user.setLastName("admin");
        user.setEmail("admin@admin.com");
        user.setPassword("test");
        List<String> pris = new ArrayList<>();
        pris.add(DefaultPrivileges.ACCESS_MODELER);
        pris.add(DefaultPrivileges.ACCESS_IDM);
        pris.add(DefaultPrivileges.ACCESS_ADMIN);
        pris.add(DefaultPrivileges.ACCESS_TASK);
        pris.add(DefaultPrivileges.ACCESS_REST_API);
        user.setPrivileges(pris);
        return user;
    }

    public static FlowableAppUser getCurrentFlowableAppUser() {
        FlowableAppUser user = null;
        SecurityContext securityContext = SecurityContextHolder.getContext();
        if (securityContext != null && securityContext.getAuthentication() != null) {
            Object principal = securityContext.getAuthentication().getPrincipal();
            if (principal instanceof FlowableAppUser) {
                user = (FlowableAppUser) principal;
            }
        }
        return user;
    }

    public static boolean currentUserHasCapability(String capability) {
        FlowableAppUser user = getCurrentFlowableAppUser();
        for (GrantedAuthority grantedAuthority : user.getAuthorities()) {
            if (capability.equals(grantedAuthority.getAuthority())) {
                return true;
            }
        }
        return false;
    }

    public static void assumeUser(User user) {
        assumeUser = user;
    }

    public static void clearAssumeUser() {
        assumeUser = null;
    }

}

3)创建包:org.flowable.ui.modeler.conf 添加安全配置类:


import org.flowable.ui.common.properties.FlowableRestAppProperties;
import org.flowable.ui.common.security.ActuatorRequestMatcher;
import org.flowable.ui.common.security.ClearFlowableCookieLogoutHandler;
import org.flowable.ui.common.security.DefaultPrivileges;
import org.flowable.ui.modeler.properties.FlowableModelerAppProperties;
import org.flowable.ui.modeler.security.AjaxLogoutSuccessHandler;
import org.flowable.ui.modeler.security.RemoteIdmAuthenticationProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest;
import org.springframework.boot.actuate.health.HealthEndpoint;
import org.springframework.boot.actuate.info.InfoEndpoint;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.header.writers.XXssProtectionHeaderWriter;

/**
 */
@Configuration
@EnableWebSecurity
public class SecurityConfiguration {

    private static final Logger LOGGER = LoggerFactory.getLogger(org.flowable.ui.modeler.conf.SecurityConfiguration.class);

    public static final String REST_ENDPOINTS_PREFIX = "/app/rest";

    @Autowired
    protected RemoteIdmAuthenticationProvider authenticationProvider;

//    @Bean
//    public FlowableCookieFilterRegistrationBean flowableCookieFilterRegistrationBean(RemoteIdmService remoteIdmService, FlowableCommonAppProperties properties) {
//        FlowableCookieFilterRegistrationBean filter = new FlowableCookieFilterRegistrationBean(remoteIdmService, properties);
//        filter.addUrlPatterns("/app/*");
//        filter.setRequiredPrivileges(Collections.singletonList(DefaultPrivileges.ACCESS_MODELER));
//        return filter;
//    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) {

        // Default auth (database backed)
        try {
            auth.authenticationProvider(authenticationProvider);
        } catch (Exception e) {
            LOGGER.error("Could not configure authentication mechanism:", e);
        }
    }

    @Configuration
    @Order(10)
    public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {

//        @Autowired
//        protected FlowableCookieFilterRegistrationBean flowableCookieFilterRegistrationBean;

        @Autowired
        protected AjaxLogoutSuccessHandler ajaxLogoutSuccessHandler;

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                    .sessionManagement()
                    .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                    .and()
//                    .addFilterBefore(flowableCookieFilterRegistrationBean.getFilter(), UsernamePasswordAuthenticationFilter.class)
                    .logout()
                    .logoutUrl("/app/logout")
                    .logoutSuccessHandler(ajaxLogoutSuccessHandler)
                    .addLogoutHandler(new ClearFlowableCookieLogoutHandler())
                    .and()
                    .csrf()
                    .disable() // Disabled, cause enabling it will cause sessions
                    .headers()
                    .frameOptions()
                    .sameOrigin()
                    .addHeaderWriter(new XXssProtectionHeaderWriter())
                    .and()
                    .authorizeRequests()
//                    .antMatchers(REST_ENDPOINTS_PREFIX + "/**").hasAuthority(DefaultPrivileges.ACCESS_MODELER);
                    .antMatchers(REST_ENDPOINTS_PREFIX + "/**").permitAll();
        }
    }

    //
    // BASIC AUTH
    //

    @Configuration
    @Order(1)
    public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {

        protected final FlowableRestAppProperties restAppProperties;
        protected final FlowableModelerAppProperties modelerAppProperties;

        public ApiWebSecurityConfigurationAdapter(FlowableRestAppProperties restAppProperties,
                                                  FlowableModelerAppProperties modelerAppProperties) {
            this.restAppProperties = restAppProperties;
            this.modelerAppProperties = modelerAppProperties;
        }

        protected void configure(HttpSecurity http) throws Exception {

            http
                    .sessionManagement()
                    .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                    .and()
                    .csrf()
                    .disable();

            http.antMatcher("/api/**").authorizeRequests().antMatchers("/api/**").permitAll();

//            if (modelerAppProperties.isRestEnabled()) {
//
//                if (restAppProperties.isVerifyRestApiPrivilege()) {
//                    http.antMatcher("/api/**").authorizeRequests().antMatchers("/api/**").hasAuthority(DefaultPrivileges.ACCESS_REST_API).and().httpBasic();
//                } else {
//                    http.antMatcher("/api/**").authorizeRequests().antMatchers("/api/**").authenticated().and().httpBasic();
//
//                }
//
//            } else {
//                http.antMatcher("/api/**").authorizeRequests().antMatchers("/api/**").denyAll();
//
//            }

        }
    }

    //
    // Actuator
    //

    @ConditionalOnClass(EndpointRequest.class)
    @Configuration
    @Order(5) // Actuator configuration should kick in before the Form Login there should always be http basic for the endpoints
    public static class ActuatorWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {

        protected void configure(HttpSecurity http) throws Exception {

            http
                    .sessionManagement()
                    .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                    .and()
                    .csrf()
                    .disable();

            http
                    .requestMatcher(new ActuatorRequestMatcher())
                    .authorizeRequests()
                    .requestMatchers(EndpointRequest.to(InfoEndpoint.class, HealthEndpoint.class)).authenticated()
                    .requestMatchers(EndpointRequest.toAnyEndpoint()).hasAnyAuthority(DefaultPrivileges.ACCESS_ADMIN)
                    .and().httpBasic();
        }
    }
}

6. 启动类配置

修改原有启动类:包名com.xtsz.modeler

package com.xtsz.modeler;

import org.flowable.ui.modeler.conf.ApplicationConfiguration;
import org.flowable.ui.modeler.servlet.AppDispatcherServletConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Import;

/**
 * 导入配置
 */
@Import({
        ApplicationConfiguration.class,
        AppDispatcherServletConfiguration.class
})
@SpringBootApplication
public class ModelerApplication {

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

}

7. 流程模型汉化

拷贝文件到resources目录:


源汉化文件
目标汉化文件

四、启动测试

  1. 请求地址:http://localhost:8002/flowable-modeler/

    模型流程

  2. 创建流程


    创建流程

    创建流程

    设计流程

五、常见问题

  1. Cannot convert value '2019-10-22 08:09:24.000000' from column 6 to TIMESTAMP
    原因:MySql数据库必须使用8.0.0+。
    依赖:
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

数据库配置:

# 数据库
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/workflow_flowable?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2b8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true

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

推荐阅读更多精彩内容