SpringBoot2.0整合quartz实现多定时任务动态配置,实现任务增删改,生成Cron表达式

部分内容转载自“尔笑惹千愁”,链接https://blog.csdn.net/lx1309244704/article/details/81810373

在我们日常的开发中,很多时候,定时任务都不是写死的,而是写到数据库中,从而实现定时任务的动态配置,下面就通过一个简单的示例,来实现这个功能。

一、添加依赖包

```

<!-- quartz -->

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-quartz</artifactId>

</dependency>

```

二、创建调度器

```

package com.quartz;

import org.springframework.boot.autoconfigure.quartz.SchedulerFactoryBeanCustomizer;

import org.springframework.context.annotation.Configuration;

import org.springframework.scheduling.quartz.SchedulerFactoryBean;

@Configuration

public class SchedulerConfig implements SchedulerFactoryBeanCustomizer{

@Override

public void customize(SchedulerFactoryBean schedulerFactoryBean) {

schedulerFactoryBean.setStartupDelay(2);

        schedulerFactoryBean.setAutoStartup(true);

        schedulerFactoryBean.setOverwriteExistingJobs(true);

}

}

```

三、application.yml配置

**这里需要注意的几点:**

**1、如果你的框架是已经搭建好,只需要在yml中添加quartz相关属性配置和数据库方式配置**

**2、quartz相关属性配置是在spring下管理,注意缩进格式,如果位置或者缩进不对程序启动后也不会报错**

```

server:

    port: 8003

# 默认的profile为dev,其他环境通过指定启动参数使用不同的profile,比如: 

#  测试环境:java -jar quartz-service.jar --spring.profiles.active=test 

#  生产环境:java -jar quartz-service.jar --spring.profiles.active=prod 

spring:

  datasource:

    type: com.alibaba.druid.pool.DruidDataSource  #这里是配置druid连接池,以下都是druid的配置信息

    url: jdbc:mysql://0.0.0.0:3306/quartz?useUnicode=true&characterEncoding=utf-8&useSSL=false

    driver-class-name: com.mysql.jdbc.Driver

    username: root

    password: ******


  quartz:

    #相关属性配置

    properties:

      org:

        quartz:

          scheduler:

            instanceName: clusteredScheduler

            instanceId: AUTO

          jobStore:

            class: org.quartz.impl.jdbcjobstore.JobStoreTX

            driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate

            tablePrefix: QRTZ_

            isClustered: true

            clusterCheckinInterval: 10000

            useProperties: false

          threadPool:

            class: org.quartz.simpl.SimpleThreadPool

            threadCount: 10

            threadPriority: 5

            threadsInheritContextClassLoaderOfInitializingThread: true

    #数据库方式

    job-store-type: jdbc


mybatis-plus:

  mapper-locations: classpath*:/mapper/**Mapper.xml    #把xml文件放在com.XX.mapper.*中可能会出现找到的问题,这里把他放在resource下的mapper中

  typeAliasesPackage: com.quartz.domain        #这里是实体类的位置,#实体扫描,多个package用逗号或者分号分隔

  configuration:

    map-underscore-to-camel-case: true

    cache-enabled: false

logging:

  file: quartz-service.log

  level:

    com.quartz: debug 

```

四、JobController

```

package com.south.controller;

import com.baomidou.mybatisplus.core.metadata.IPage;

import com.south.data.vo.JobAndTriggerDto;

import com.south.service.IJobAndTriggerService;

import com.south.utils.PaginationUtil;

import lombok.extern.slf4j.Slf4j;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.data.domain.Pageable;

import org.springframework.http.HttpHeaders;

import org.springframework.http.ResponseEntity;

import org.springframework.util.MultiValueMap;

import org.springframework.web.bind.annotation.*;

import org.springframework.web.util.UriComponentsBuilder;

import java.util.List;

/**

* @Classname JobController

* @Description TODO

* @Date 2019/7/31 14:11

* @Created by zhangzhenjun

*/

@Slf4j

@RestController

@RequestMapping("/api")

public class JobResource {

    @Autowired

    private IJobAndTriggerService jobAndTriggerService;

    public JobResource(IJobAndTriggerService jobAndTriggerService){

        this.jobAndTriggerService = jobAndTriggerService;

    }

    @PostMapping(value = "/datagrid")

    public ResponseEntity<List<JobAndTriggerDto>> queryjob(Pageable pageable, @RequestParam MultiValueMap<String, String> queryParams, UriComponentsBuilder uriBuilder) {

        log.debug("queryjob");

        IPage<JobAndTriggerDto> page = jobAndTriggerService.getPageJob(pageable, queryParams);

        HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(uriBuilder.queryParams(queryParams), page);

        return ResponseEntity.ok().headers(headers).body(page.getRecords());

    }

    /**

    * @Title: addJob

    * @Description: TODO(添加Job)

    * @param jobClassName

    *            类名

    * @param jobGroupName

    *            组名

    * @param cronExpression

    *            表达式,如:0/5 * * * * ? (每隔5秒)

    */

    @PostMapping(value = "/add")

    public ResponseEntity addJob(

            @RequestParam(value = "jobClassName") String jobClassName,

            @RequestParam(value = "jobGroupName") String jobGroupName,

            @RequestParam(value = "cronExpression") String cronExpression){

        try {

            jobAndTriggerService.addJob(jobClassName, jobGroupName, cronExpression);

            return ResponseEntity.ok().body("操作成功");

        } catch (Exception e) {

            e.printStackTrace();

            return ResponseEntity.ok().body("操作失败");

        }

    }

    /**

    * @Title: pauseJob

    * @Description: TODO(暂停Job)

    * @param jobClassName

    *            类名

    * @param jobGroupName

    *            组名

    */

    @PostMapping(value = "/pause")

    public ResponseEntity pauseJob(

            @RequestParam(value = "jobClassName") String jobClassName,

            @RequestParam(value = "jobGroupName") String jobGroupName) {

        try {

            jobAndTriggerService.pauseJob(jobClassName, jobGroupName);

            return ResponseEntity.ok().body("操作成功");

        } catch (Exception e) {

            e.printStackTrace();

            return ResponseEntity.ok().body("操作失败");

        }

    }

    /**

    * @Title: resumeJob

    * @Description: TODO(恢复Job)

    * @param jobClassName

    *            类名

    * @param jobGroupName

    *            组名

    */

    @PostMapping(value = "/resume")

    public ResponseEntity resumeJob(

            @RequestParam(value = "jobClassName") String jobClassName,

            @RequestParam(value = "jobGroupName") String jobGroupName) {

        try {

            jobAndTriggerService.resumejob(jobClassName, jobGroupName);

            return ResponseEntity.ok().body("操作成功");

        } catch (Exception e) {

            e.printStackTrace();

            return ResponseEntity.ok().body("操作失败");

        }

    }

    /**

    * @Title: rescheduleJob

    * @Description: TODO(重新设置Job)

    * @param jobClassName

    *            类名

    * @param jobGroupName

    *            组名

    * @param cronExpression

    *            表达式

    */

    @PostMapping(value = "/reschedule")

    public ResponseEntity rescheduleJob(

            @RequestParam(value = "jobClassName") String jobClassName,

            @RequestParam(value = "jobGroupName") String jobGroupName,

            @RequestParam(value = "cronExpression") String cronExpression) {

        try {

            jobAndTriggerService.updateJob(jobClassName, jobGroupName, cronExpression);

            return ResponseEntity.ok().body("操作成功");

        } catch (Exception e) {

            e.printStackTrace();

            return ResponseEntity.ok().body("操作失败");

        }

    }

    /**

    * @Title: deleteJob

    * @Description: TODO(删除Job)

    * @param jobClassName

    *            类名

    * @param jobGroupName

    *            组名

    */

    @RequestMapping(value = "/del", method = RequestMethod.POST)

    public ResponseEntity deleteJob(@RequestParam(value = "jobClassName") String jobClassName, @RequestParam(value = "jobGroupName") String jobGroupName) {

        try {

            jobAndTriggerService.deleteJob(jobClassName, jobGroupName);

            return ResponseEntity.ok().body("操作成功");

        } catch (Exception e) {

            e.printStackTrace();

            return ResponseEntity.ok().body("操作失败");

        }

    }

}

```

五、IJobAndTriggerService

```

package com.south.service;

import com.baomidou.mybatisplus.core.metadata.IPage;

import com.baomidou.mybatisplus.extension.service.IService;

import com.south.data.vo.JobAndTriggerDto;

import org.springframework.data.domain.Pageable;

import org.springframework.util.MultiValueMap;

import java.util.Map;

/**

* @Classname IJobAndTriggerService

* @Description TODO

* @Date 2019/7/31 14:15

* @Created by zhangzhenjun

*/

public interface IJobAndTriggerService extends IService<JobAndTriggerDto> {

    /**

    * @Title: getPageJob

    * @Description: TODO(查询定时任务,分页)

    * @param @param search

    * @param @return    参数

    * @return Map<String,Object>    返回类型

    * @throws

    */

    IPage<JobAndTriggerDto> getPageJob(Pageable pageable, MultiValueMap queryParam);

    /**

    * @Title: getPageJobmod

    * @Description: TODO(查询定时任务)

    * @param @return    参数

    * @return JobAndTriggerDto    返回类型

    * @throws

    */

    JobAndTriggerDto getPageJobmod();

    /**

    * @Title: addJob

    * @Description: TODO(添加任务)

    * @param @param jobClassName 任务路径名称

    * @param @param jobGroupName 任务分组

    * @param @param cronExpression cron时间规则

    * @param @throws Exception    参数

    * @return void    返回类型

    * @throws

    */

    void addJob(String jobClassName, String jobGroupName, String cronExpression) throws Exception;

    /**

    * @Title: addJob

    * @Description: TODO(添加动态任务)

    * @param @param jobClassName 任务路径名称

    * @param @param jobGroupName 任务分组

    * @param @param cronExpression cron时间规则

    * @param @param jobDescription 参数

    * @param @param params

    * @param @throws Exception  参数说明

    * @return void    返回类型

    * @throws

    */

    void addJob(String jobClassName, String jobGroupName, String cronExpression, String jobDescription, Map<String, Object> params) throws Exception;

    /**

    * @Title: updateJob

    * @Description: TODO(更新定时任务)

    * @param @param jobClassName 任务路径名称

    * @param @param jobGroupName 任务分组

    * @param @param cronExpression cron时间规则

    * @param @throws Exception    参数

    * @return void    返回类型

    * @throws

    */

    void updateJob(String jobClassName, String jobGroupName, String cronExpression) throws Exception;

    /**

    * @Title: deleteJob

    * @Description: TODO(删除定时任务)

    * @param @param jobClassName 任务路径名称

    * @param @param jobGroupName 任务分组

    * @param @throws Exception    参数

    * @return void    返回类型

    * @throws

    */

    void deleteJob(String jobClassName, String jobGroupName) throws Exception;

    /**

    * @Title: pauseJob

    * @Description: TODO(暂停定时任务)

    * @param @param jobClassName 任务路径名称

    * @param @param jobGroupName 任务分组

    * @param @throws Exception    参数

    * @return void    返回类型

    * @throws

    */

    void pauseJob(String jobClassName, String jobGroupName) throws Exception;

    /**

    * @Title: resumejob

    * @Description: TODO(恢复任务)

    * @param @param jobClassName 任务路径名称

    * @param @param jobGroupName 任务分组

    * @param @throws Exception    参数

    * @return void    返回类型

    * @throws

    */

    void resumejob(String jobClassName, String jobGroupName) throws Exception;

}

```

六、IJobAndTriggerServiceImpl

```

package com.south.service.impl;

import com.baomidou.mybatisplus.core.metadata.IPage;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

import com.south.data.mapper.JobAndTriggerMapper;

import com.south.data.vo.JobAndTriggerDto;

import com.south.job.BaseJob;

import com.south.service.IJobAndTriggerService;

import lombok.extern.slf4j.Slf4j;

import org.quartz.*;

import org.quartz.CronTrigger;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.data.domain.Pageable;

import org.springframework.stereotype.Service;

import org.springframework.transaction.annotation.Transactional;

import org.springframework.util.MultiValueMap;

import java.util.Iterator;

import java.util.Map;

/**

* @Classname IJobAndTriggerServiceImpl

* @Description TODO

* @Date 2019/7/31 14:30

* @Created by zhangzhenjun

*/

@Slf4j

@Service

@Transactional

public class IJobAndTriggerServiceImpl extends ServiceImpl<JobAndTriggerMapper, JobAndTriggerDto> implements IJobAndTriggerService {

    @Autowired

    private Scheduler scheduler;

    @Override

    public IPage<JobAndTriggerDto> getPageJob(Pageable pageable, MultiValueMap queryParam) {

        IPage<JobAndTriggerDto> page = new Page<>(pageable.getPageNumber(), pageable.getPageSize());

        return baseMapper.getJobAndTriggerDetails(page);

    }

    @Override

    public JobAndTriggerDto getPageJobmod() {

        return baseMapper.getJobAndTriggerDto();

    }

    @Override

    public void addJob(String jobClassName, String jobGroupName, String cronExpression) throws Exception {

        // 启动调度器

        scheduler.start();

        // 构建job信息

        JobDetail jobDetail = JobBuilder.newJob(getClass(jobClassName).getClass())

                .withIdentity(jobClassName, jobGroupName).build();

        // 表达式调度构建器(即任务执行的时间)

        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);

        // 按新的cronExpression表达式构建一个新的trigger

        CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(jobClassName, jobGroupName)

                .withSchedule(scheduleBuilder).build();

        try {

            scheduler.scheduleJob(jobDetail, trigger);

            System.out.println("创建定时任务成功");

        } catch (SchedulerException e) {

            System.out.println("创建定时任务失败" + e);

            throw new Exception("创建定时任务失败");

        }

    }

    @Override

    public void addJob(String jobClassName, String jobGroupName, String cronExpression, String jobDescription,

                      Map<String, Object> params) throws Exception {

        // 启动调度器

        scheduler.start();

        // 构建job信息

        JobDetail jobDetail = JobBuilder.newJob(IJobAndTriggerServiceImpl.getClass(jobClassName).getClass())

                .withIdentity(jobClassName, jobGroupName).withDescription(jobDescription).build();

        Iterator<Map.Entry<String, Object>> var7 = params.entrySet().iterator();

        while(var7.hasNext()) {

            Map.Entry<String, Object> entry = var7.next();

            jobDetail.getJobDataMap().put((String)entry.getKey(), entry.getValue());

        }

        System.out.println("jobDetail数据:--------"+jobDetail.toString());

        // 表达式调度构建器(即任务执行的时间)

        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);

        // 按新的cronExpression表达式构建一个新的trigger

        CronTrigger trigger = (CronTrigger)TriggerBuilder.newTrigger().withIdentity(jobClassName, jobGroupName)

                .withSchedule(scheduleBuilder).build();

        try {

            scheduler.scheduleJob(jobDetail, trigger);

            System.out.println("创建定时任务成功");

        } catch (SchedulerException e) {

            System.out.println("创建定时任务失败" + e);

            throw new Exception("创建定时任务失败");

        }

    }

    @Override

    public void updateJob(String jobClassName, String jobGroupName, String cronExpression) throws Exception {

        try {

            TriggerKey triggerKey = TriggerKey.triggerKey(jobClassName, jobGroupName);

            // 表达式调度构建器(动态修改后不立即执行)

            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing();

            CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);

            // 按新的cronExpression表达式重新构建trigger

            trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();

            // 按新的trigger重新设置job执行

            scheduler.rescheduleJob(triggerKey, trigger);

        } catch (SchedulerException e) {

            System.out.println("更新定时任务失败" + e);

            throw new Exception("更新定时任务失败");

        }

    }

    @Override

    public void deleteJob(String jobClassName, String jobGroupName) throws Exception {

        scheduler.pauseTrigger(TriggerKey.triggerKey(jobClassName, jobGroupName));

        scheduler.unscheduleJob(TriggerKey.triggerKey(jobClassName, jobGroupName));

        scheduler.deleteJob(JobKey.jobKey(jobClassName, jobGroupName));

    }

    @Override

    public void pauseJob(String jobClassName, String jobGroupName) throws Exception {

        scheduler.pauseJob(JobKey.jobKey(jobClassName, jobGroupName));

    }

    @Override

    public void resumejob(String jobClassName, String jobGroupName) throws Exception {

        scheduler.resumeJob(JobKey.jobKey(jobClassName, jobGroupName));

    }

    public static BaseJob getClass(String classname) throws Exception {

        Class<?> class1 = Class.forName(classname);

        return (BaseJob) class1.newInstance();

    }

}

```

七、JobAndTriggerMapper

```

package com.south.data.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;

import com.baomidou.mybatisplus.core.metadata.IPage;

import com.south.data.vo.JobAndTriggerDto;

/**

* @Classname JobAndTriggerMapper

* @Description TODO

* @Date 2019/7/31 14:31

* @Created by zhangzhenjun

*/

public interface JobAndTriggerMapper extends BaseMapper<JobAndTriggerDto> {

    IPage<JobAndTriggerDto> getJobAndTriggerDetails(IPage<JobAndTriggerDto> page);

    JobAndTriggerDto getJobAndTriggerDto();

}

```

八、JobAndTriggerMapper.xml

```

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.south.data.mapper.JobAndTriggerMapper">

    <select id="getJobAndTriggerDetails" resultType="com.south.data.vo.JobAndTriggerDto">

SELECT

jd.JOB_NAME AS jobName,

jd.DESCRIPTION AS jobDescription,

jd.JOB_GROUP AS jobGroupName,

jd.JOB_CLASS_NAME AS jobClassName,

t.TRIGGER_NAME AS triggerName,

t.TRIGGER_GROUP AS triggerGroupName,

FROM_UNIXTIME(t.PREV_FIRE_TIME/1000,'%Y-%m-%d %T') AS prevFireTime,

FROM_UNIXTIME(t.NEXT_FIRE_TIME/1000,'%Y-%m-%d %T') AS nextFireTime,

ct.CRON_EXPRESSION AS cronExpression,

t.TRIGGER_STATE AS triggerState

FROM

qrtz_job_details jd

JOIN qrtz_triggers t

JOIN qrtz_cron_triggers ct ON jd.JOB_NAME = t.JOB_NAME

AND t.TRIGGER_NAME = ct.TRIGGER_NAME

AND t.TRIGGER_GROUP = ct.TRIGGER_GROUP

    </select>

    <select id="getJobAndTriggerDto" resultType="com.south.data.vo.JobAndTriggerDto">

SELECT

jd.JOB_NAME AS jobName,

jd.DESCRIPTION AS jobDescription,

jd.JOB_GROUP AS jobGroupName,

jd.JOB_CLASS_NAME AS jobClassName,

t.TRIGGER_NAME AS triggerName,

t.TRIGGER_GROUP AS triggerGroupName,

FROM_UNIXTIME(t.PREV_FIRE_TIME/1000,'%Y-%m-%d %T') AS prevFireTime,

FROM_UNIXTIME(t.NEXT_FIRE_TIME/1000,'%Y-%m-%d %T') AS nextFireTime,

ct.CRON_EXPRESSION AS cronExpression,

t.TRIGGER_STATE AS triggerState

FROM

qrtz_job_details jd

JOIN qrtz_triggers t

JOIN qrtz_cron_triggers ct ON jd.JOB_NAME = t.JOB_NAME

AND t.TRIGGER_NAME = ct.TRIGGER_NAME

AND t.TRIGGER_GROUP = ct.TRIGGER_GROUP

    </select>

</mapper>

```

九、BaseJob

```

package com.south.job;

import org.quartz.Job;

import org.quartz.JobExecutionContext;

import org.quartz.JobExecutionException;

/**

* @Classname BaseJob

* @Description TODO

* @Date 2019/7/31 14:52

* @Created by zhangzhenjun

*/

public interface BaseJob extends Job {

  public void execute(JobExecutionContext context) throws JobExecutionException;

}

```

十、HelloJob实例,在这里面写定时任务要执行的内容

```

package com.south.job;

import lombok.extern.slf4j.Slf4j;

import org.quartz.JobExecutionContext;

import org.quartz.JobExecutionException;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import java.util.Date;

/**

* @Classname HelloJob

* @Description TODO

* @Date 2019/7/31 14:10

* @Created by zhangzhenjun

*/

@Slf4j

public class HelloJob implements BaseJob {

    @Override

    public void execute(JobExecutionContext context) throws JobExecutionException {

        log.error("Hello Job执行时间: " + new Date());

        System.err.println("Hello Job执行时间: " + new Date());

    } 

```

十一、启动类加上@EnableScheduling注解,在项目启动时加载定时任务,启动Job

![在这里插入图片描述](https://img-blog.csdnimg.cn/20190802091619840.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyNTY3ODAx,size_16,color_FFFFFF,t_70)

项目效果:

![在这里插入图片描述](https://img-blog.csdnimg.cn/2019080209184473.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyNTY3ODAx,size_16,color_FFFFFF,t_70)

![在这里插入图片描述](https://img-blog.csdnimg.cn/20190802091904900.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyNTY3ODAx,size_16,color_FFFFFF,t_70)

如果需要源码,请跳转自本博文最上方的转载链接;

***最后再附上Java转Cron表达式的工具类,具体代码在我的另一篇博文:java生成cron表达式***

***链接:https://blog.csdn.net/qq_42567801/article/details/98172088***

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

推荐阅读更多精彩内容