自定義全局自增ID生成器

看了網(wǎng)上很多生成自增ID的策略搪桂,最終給出的都是雪花算法螃壤,leaf算法。但是卻沒(méi)有滿足咱們對(duì)于自定義生成規(guī)則的需求倡怎。

在業(yè)務(wù)上有一部分ID往往是有規(guī)則的,比如某個(gè)產(chǎn)品的訂單號(hào)往往是“產(chǎn)品標(biāo)志+時(shí)間+n位流水”贱枣,類似這樣的訂單規(guī)則监署,使用雪花算法是滿足不了業(yè)務(wù)需求的,所以我們得設(shè)計(jì)一套自己的自定義ID生成器纽哥。

“產(chǎn)品標(biāo)志+時(shí)間+n位流水”規(guī)則中钠乏,難點(diǎn)無(wú)非在于n位流水號(hào)的生成,因?yàn)檫@個(gè)流水號(hào)需要保證在多次請(qǐng)求中不會(huì)產(chǎn)生重復(fù)的訂單號(hào)春塌。

首先晓避,咱們根據(jù)業(yè)務(wù)需求,先制定對(duì)應(yīng)的規(guī)則表達(dá)式:

id:
  generator:
    expressions:
        # 產(chǎn)品標(biāo)志
      TEST:
        # 表達(dá)式只壳,pid指的是產(chǎn)品標(biāo)志
        exp: "$(pid)$(yearMonthDayHms)$(id:6:0)"
        # 字段名:初始值:最大值:最小數(shù)量:擴(kuò)容數(shù)量:初始數(shù)量:增長(zhǎng)步長(zhǎng)
        initFields: ["id"]

表達(dá)式對(duì)應(yīng)的實(shí)體類:

@Data
@Configuration
@ConfigurationProperties(prefix = "id.generator")
public class IDExpressionProperties {

    private Map<String, SerialIdConfig> expressions;

    @Data
    public static class SerialIdConfig {
        // 表達(dá)式
        private String exp;
        // 初始化字段
        private String[] initFields;
    }
}

通過(guò)spring解析獲得對(duì)應(yīng)的IDExpressionProperties實(shí)體類俏拱,拿到咱們自定義的配置

通過(guò)規(guī)則表達(dá)式可以看出,類似“$(pid)”這樣的表達(dá)式吼句,咱們可以抽象成接口自定義生成锅必。比如咱們定義一個(gè)VariableGenerator接口或者抽象類,遇到pid就從spring ioc容器中調(diào)用pidVariableGenerator這個(gè)Bean的生成方法獲取pid的值惕艳。遇到y(tǒng)earMonthDayHms就調(diào)用yearMonthDayHmsVariableGenerator這個(gè)Bean的生成方法獲取yearMonthDayHms指定的值搞隐。

@Configuration
public class SerialConfig {

    @Autowired private IDExpressionProperties idExpressionProperties;
        /**
        ** 咱們要?jiǎng)?chuàng)建一個(gè)ID工廠類驹愚,專門用來(lái)生成ID的類
    ** 使用方法:
    ** @Autowired
    ** private IDFactory idFactory;
    ** String id = idFactory.get("產(chǎn)品標(biāo)志");
    */
    @Bean(initMethod = "init")
    public IDFactory serialIdFactory() {
        return new IDFactory(idExpressionProperties.getExpressions());
    }
}

變量生成器

import org.apache.commons.lang3.StringUtils;

/**
 * 變量生成器
 * @className VariableGenerator
 * @date: 2021/2/18 下午2:53
 * @description:
 */
public abstract class VariableGenerator {

    public static final String COLON = ":";
   /**
        * apply是生成目標(biāo)字符串的方法
        */
    protected abstract String apply(ExpressionElement e, Expression expression);

   /**
        * apply的后置處理方法劣纲,默認(rèn)處理字符串不足的情況下逢捺,補(bǔ)足對(duì)應(yīng)的填充數(shù)據(jù)
        */
    public String andThen(ExpressionElement e, Expression expression) {
        String variableValue = apply(e, expression);
        int count = e.getCount();
        String fillStringValue = e.getFillStringValue();
        if (StringUtils.isNotBlank(variableValue)) {
            if (count > 0) {
                variableValue = StringUtils.leftPad(variableValue, count, fillStringValue);
            } else {
                variableValue =
                        StringUtils.rightPad(variableValue, Math.abs(count), fillStringValue);
            }
        }
        return variableValue;
    }
}

ID生成器

  • 初始化的時(shí)候根據(jù)表達(dá)式配置確定是哪些字段需要初始化,根據(jù)初始化字段調(diào)用指定的Bean執(zhí)行初始化
  • 通過(guò)get方法傳入的參數(shù)key獲取指定的規(guī)則表達(dá)式癞季,根據(jù)指定的表達(dá)式調(diào)用對(duì)應(yīng)的生成器Bean實(shí)例蒸甜,調(diào)用指定的方法生成目標(biāo)值,最后拼接出最終的ID
public class IDFactory {
    // 變量生成器
    @Autowired(required = false)
    private Map<String, VariableGenerator> variableGeneratorMap;
        // 字段初始化生成器
    @Autowired(required = false)
    private Map<String, InitFieldGenerator> initFieldGeneratorMap;
        // 構(gòu)造函數(shù)余佛,參數(shù)是生成規(guī)則
    public IDFactory(Map<String, IDExpressionProperties.SerialIdConfig> expressionMap) {
        this.expressionMap = expressionMap;
    }
        // 實(shí)例化后執(zhí)行
    public void init() {
        // 如果沒(méi)有規(guī)則表達(dá)式柠新,那么直接就結(jié)束
        if (CollectionUtils.isEmpty(this.expressionMap)) {
            return;
        }
        for (Map.Entry<String, IDExpressionProperties.SerialIdConfig> e :
                this.expressionMap.entrySet()) {
            String key = e.getKey();
            // 規(guī)則表達(dá)式
            IDExpressionProperties.SerialIdConfig config = e.getValue();
            // 初始化字段參數(shù)
            String[] initFields = config.getInitFields();
            // 如果沒(méi)有初始化字段生成器,直接結(jié)束
            if (CollectionUtils.isEmpty(initFieldGeneratorMap)) {
                return;
            }
            // 根據(jù)初始化規(guī)則辉巡,執(zhí)行初始化操作
            for (String initField : initFields) {
                String fieldName = initField;
                // 獲取初始化字段名稱
                if (StringUtils.contains(initField, VariableGenerator.COLON)) {
                    fieldName = StringUtils.substringBefore(initField, VariableGenerator.COLON);
                }
                // 根據(jù)字段名稱獲取對(duì)應(yīng)的初始化生成器的Bean實(shí)例
                InitFieldGenerator initFieldGenerator =
                        initFieldGeneratorMap.get(
                                fieldName + InitFieldGenerator.INIT_FIELD_GENERATOR);
                if (Objects.nonNull(initFieldGenerator)) {
                    // 執(zhí)行字段初始化操作
                    initFieldGenerator.generator(key, initField);
                }
            }
        }
    }

    /**
     * 表達(dá)式
     *
     * <p>pid:expression格式
     */
    private Map<String, IDExpressionProperties.SerialIdConfig> expressionMap;
    /**
     * 根據(jù)指定的key規(guī)則生成id
     *
     * @param key
     * @return
     */
    public String get(String key) {
        // key為空直接拋異常
        if (StringUtils.isBlank(key)) {
            throw new IllegalArgumentException("無(wú)效的參數(shù)值:" + key);
        }
        // 獲取規(guī)則表達(dá)式
        IDExpressionProperties.SerialIdConfig serialIdConfig = expressionMap.get(key);
        // 表達(dá)式字符串
        String expressionString = serialIdConfig.getExp();
        // 為空直接拋異常
        if (StringUtils.isBlank(expressionString)) {
            throw new IllegalArgumentException("沒(méi)有找到對(duì)應(yīng)的表達(dá)式");
        }
        // 解析指定的表達(dá)式
        Expression expression = parse(key, expressionString);
        // 匹配得出最終結(jié)果
        return matchExpression(expression);
    }
        // 生成器名稱后綴
    private static final String VARIABLE_GENERATOR = "VariableGenerator";
  
        // 循環(huán)遍歷表達(dá)式中所有的自定義變量恨憎,獲取指定Bean實(shí)例,執(zhí)行目標(biāo)方法后得出最終ID
    private String matchExpression(Expression expression) {
        // 獲取變量列表郊楣,例如pid憔恳,yearMonthDayHms等
        List<ExpressionElement> elements = expression.getElements();
        // 如果沒(méi)有任何變量,那么直接返回原表達(dá)式净蚤,說(shuō)明表達(dá)式是一個(gè)常量
        if (CollectionUtils.isEmpty(elements)) {
            return expression.getExpression();
        }
        // 獲取原表達(dá)式钥组,用來(lái)替換變量,生成最終的ID
        String expressionString = expression.getExpression();
        // 循環(huán)遍歷變量列表
        for (ExpressionElement e : elements) {
            // 拼接Bean的名稱
            String beanName = e.getVariableName() + VARIABLE_GENERATOR;
            // 從map中取出指定的Bean
            VariableGenerator variableGenerator = variableGeneratorMap.get(beanName);
            // 如果沒(méi)有取到今瀑,那么直接忽略程梦,說(shuō)明沒(méi)有創(chuàng)建該表達(dá)式對(duì)應(yīng)的生成器
            if (Objects.isNull(variableGenerator)) {
                continue;
            }
            // 調(diào)用目標(biāo)方法生成字符串
            String variableValue = variableGenerator.andThen(e, expression);
            // 如果不為空,就替換掉原表達(dá)式中的變量橘荠;就是用具體生成的值替換變量表達(dá)式
            // “$(pid)$(yearMonthDayHms)$(id:6:0)”會(huì)被替換成“TEST$(yearMonthDayHms)$(id:6:0)”
            if (StringUtils.isNotBlank(variableValue)) {
                expressionString =
                        StringUtils.replace(expressionString, e.getOriginString(), variableValue);
            }
        }
        // 返回最終結(jié)果
        return expressionString;
    }
        // 正則表達(dá)式屿附,用來(lái)解析$(pid)$(yearMonthDayHms)$(id:6:0)表達(dá)式
    private static final Pattern EXPRESSION_PATTERN = Pattern.compile("\\$\\((.+?)\\)");

    private static final Map<String, Expression> EXPRESSION_MAP = Maps.newConcurrentMap();
    /**
     * 解析$(pid)$(yearMonthDayHms)$(id:6:0)
     *
     * @param expressionString
     * @return
     */
    private Expression parse(String key, String expressionString) {
        // 檢查一下緩存中是否有解析號(hào)的表達(dá)式
        Expression expression = EXPRESSION_MAP.get(key);
        // 緩存不為空的話,直接返回
        if (Objects.nonNull(expression)) {
            return expression;
        }
        // 否則哥童,直接解析
        synchronized (EXPRESSION_MAP) {
            // 雙重檢查挺份,避免重復(fù)解析
            expression = EXPRESSION_MAP.get(key);
            if (Objects.nonNull(expression)) {
                return expression;
            }
            // 生成表達(dá)式對(duì)象
            expression = new Expression();
            expression.setKey(key);
            expression.setExpression(expressionString);
            List<ExpressionElement> expressionElements = Lists.newArrayList();
            Matcher matcher = EXPRESSION_PATTERN.matcher(expressionString);
            while (matcher.find()) {
                // 正則表達(dá)式,找出$()變量表達(dá)式贮懈,類似id:6:0
                String expressionVariable = matcher.group(1);
                // 表達(dá)式切割匀泊,分離出冒號(hào)分隔的參數(shù)
                String[] expressionVariables =
                        StringUtils.splitByWholeSeparatorPreserveAllTokens(
                                expressionVariable, VariableGenerator.COLON);
                ExpressionElement expre = new ExpressionElement();
                // 變量名稱id
                expre.setVariableName(expressionVariables[0]);
                // 原生表達(dá)式$(id:6:0),便于后面直接替換
                expre.setOriginString(matcher.group());
                if (expressionVariables.length > 1) {
                    // 獲取填充的最終長(zhǎng)度
                    expre.setCount(CastUtil.castInt(expressionVariables[1]));
                }
                if (expressionVariables.length > 2) {
                    // 獲取填充值
                    expre.setFillStringValue(expressionVariables[2]);
                }
                expressionElements.add(expre);
            }
            expression.setElements(expressionElements);
            // 放入本地緩存
            EXPRESSION_MAP.put(key, expression);
        }
        // 返回解析出來(lái)的表達(dá)式
        return expression;
    }
}
import lombok.Data;

import java.util.List;

/**
 * @className Expression
 * @date: 2021/2/18 下午2:53
 * @description: 解析$(pid)$(year)$(month)$(day)$(id:6:0)這種類型的表達(dá)式
 */
@Data
public class Expression {
    /** pid */
    private String key;
    /** 表達(dá)式 */
    private String expression;
    /** 解析結(jié)果 */
    private List<ExpressionElement> elements;
}

/**
 * @author zouwei
 * @className ExpressionElement
 * @date: 2021/2/18 下午2:56
 * @description: 解析${id:6:0}這種類型的標(biāo)記
 */
@Data
public class ExpressionElement {
    // 原生變量表達(dá)式
    private String originString;
    // 變量名稱
    private String variableName;
    // 總長(zhǎng)度
    private int count;
        // 填充值朵你,默認(rèn)是空字符
    private String fillStringValue = StringUtils.SPACE;
}

初始化字段生成器

public abstract class InitFieldGenerator {

    public static final String INIT_FIELD_GENERATOR = "InitFieldGenerator";
    // 執(zhí)行初始化操作
    public abstract String generator(String key, String initField);
}

以上代碼咱們已經(jīng)把整體的初始化各聘、ID生成邏輯全部搞定,剩下的就是需要把對(duì)應(yīng)的接口填充完畢就行撬呢。

針對(duì)表達(dá)式

$(pid)$(yearMonthDayHms)$(id:6:0)

咱們分別需要實(shí)現(xiàn)pidVariableGenerator伦吠、yearMonthDayHmsVariableGenerator、idVariableGenerator

@Bean
public VariableGenerator pidVariableGenerator() {
    return new VariableGenerator() {
        @Override
        public String apply(ExpressionElement e, Expression expression) {
            return expression.getKey();
        }
    };
}

private static final String YEAR_MONTH_DAY_HOUR_MINUTE_SECOND_FORMAT = "yyyyMMddHHmmss";

@Bean
public VariableGenerator yearMonthDayHmsVariableGenerator() {
    return new VariableGenerator() {
        @Override
        public String apply(ExpressionElement e, Expression expression) {
            return DateUtil.format(new DateTime(), YEAR_MONTH_DAY_HOUR_MINUTE_SECOND_FORMAT);
        }
    };
}

因?yàn)樵蹅兊淖栽鰅d是使用的redis的lua腳本實(shí)現(xiàn)的,所以會(huì)依賴redis毛仪。利用了redis執(zhí)行l(wèi)ua腳本的原子性搁嗓。

@Bean
public SerialIDVariableGenerator idVariableGenerator() {
    return new SerialIDVariableGenerator();
}

public class SerialIDVariableGenerator extends VariableGenerator {

    @Autowired private RedisTemplate redisTemplate;

    private InitParams initParams;
        // 構(gòu)造函數(shù)
    public void initParams(String key, String initFields) {
        this.initParams = parse(key, initFields);
    }
    /**
     *  解析表達(dá)式   字段名:初始值:最大值:最小數(shù)量:擴(kuò)容數(shù)量:初始數(shù)量:增長(zhǎng)步長(zhǎng)
     *
     * @param initField
     */
    private InitParams parse(String key, String initField) {
        InitParams initParams = new InitParams();
        if (StringUtils.contains(initField, COLON)) {
            String[] params = StringUtils.splitByWholeSeparatorPreserveAllTokens(initField, COLON);
            initParams.setFieldName(key + COLON + params[0]);
            initParams.setField(params);
        } else {
            initParams.setFieldName(key + COLON + initField);
            initParams.updateFields();
        }
        return initParams;
    }
        // 執(zhí)行l(wèi)ua腳本,生成對(duì)應(yīng)的自增id
    public String generate() {
        String fieldName = this.initParams.getFieldName();
        return executeLua(
                fieldName,
                initParams.getInitValue(),
                initParams.getMaxValue(),
                initParams.getMinCount(),
                initParams.getInitCount(),
                initParams.getExpansionStep(),
                initParams.getIncrStep());
    }
        // 執(zhí)行生成函數(shù)
    @Override
    protected String apply(ExpressionElement e, Expression expression) {
        return generate();
    }
        // 執(zhí)行l(wèi)ua腳本
    private String executeLua(
            String key,
            int initValue,
            int maxValue,
            int minCount,
            int initCount,
            int expansionStep,
            int incrStep) {
        // 執(zhí)行l(wèi)ua腳本
        DefaultRedisScript<String> defaultRedisScript = new DefaultRedisScript<>();
        defaultRedisScript.setResultType(String.class);
        defaultRedisScript.setScriptText(LUA_SCRIPT);
        RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
        String result =
                CastUtil.castString(
                        redisTemplate.execute(
                                defaultRedisScript,
                                serializer,
                                serializer,
                                Lists.newArrayList(key),
                                CastUtil.castString(initValue),
                                CastUtil.castString(maxValue),
                                CastUtil.castString(minCount),
                                CastUtil.castString(initCount),
                                CastUtil.castString(expansionStep),
                                CastUtil.castString(incrStep)));
        return result;
    }

    @Data
    private static class InitParams {

        /** 默認(rèn)初始值 */
        private static final int DEFAULT_INIT_VALUE = 1;
        /** 默認(rèn)最大值 */
        private static final int DEFAULT_MAX_VALUE = 9999;
        /** 默認(rèn)最小數(shù)量 */
        private static final int DEFAULT_MIN_COUNT = 30;
        /** 默認(rèn)初始數(shù)量 */
        private static final int DEFAULT_INIT_COUNT = 100;
        /** 默認(rèn)擴(kuò)容數(shù)量 */
        private static final int DEFAULT_EXPANSION_STEP = 50;
        /** 默認(rèn)自增步長(zhǎng) */
        private static final int DEFAULT_INCR_STEP = 1;

        private final int[] params = {
            0,
            DEFAULT_INIT_VALUE,
            DEFAULT_MAX_VALUE,
            DEFAULT_MIN_COUNT,
            DEFAULT_EXPANSION_STEP,
            DEFAULT_INIT_COUNT,
            DEFAULT_INCR_STEP
        };
        /** 字段名稱箱靴,其實(shí)就是key */
        private String fieldName;
        /** 初始值 */
        private int initValue;
        /** 最大值 */
        private int maxValue;
        /** 最小數(shù)量 */
        private int minCount;
        /** 擴(kuò)容步長(zhǎng) */
        private int expansionStep;
        /** 初始數(shù)量 */
        private int initCount;
        /** 自增步長(zhǎng) */
        private int incrStep;

        public void setField(Object[] objects) {
            if (ArrayUtils.isEmpty(objects) || ArrayUtils.getLength(objects) < 2) {
                return;
            }
            for (int i = 1; i < objects.length; i++) {
                Object obj = objects[i];
                params[i] = CastUtil.castInt(obj);
            }
            updateFields();
        }

        public void updateFields() {
            this.initValue = params[1];
            this.maxValue = params[2];
            this.minCount = params[3];
            this.expansionStep = params[4];
            this.initCount = params[5];
            this.incrStep = params[6];
        }
    }
        // 該腳本的執(zhí)行邏輯
    // 在redis中生成一個(gè)隊(duì)列腺逛,指定初始化長(zhǎng)度,第一個(gè)初始值衡怀,最大值棍矛,隊(duì)列最小數(shù)量,每次擴(kuò)容的數(shù)量抛杨,自增的步長(zhǎng)
    // 1.如果隊(duì)列不存在够委,就初始化隊(duì)列,按照給定的初始化長(zhǎng)度怖现,初始值茁帽,自增步長(zhǎng),最大值等參數(shù)創(chuàng)建一個(gè)隊(duì)列
    // 2.如果隊(duì)列中值的數(shù)量超過(guò)隊(duì)列最小數(shù)量屈嗤,那么直接pop出一個(gè)值
    // 3.如果小于最小數(shù)量潘拨,那么直接循環(huán)生成指定步長(zhǎng)的自增ID
    // 4.最終會(huì)pop出第一個(gè)數(shù)值
    // 5.如果是初始化的話,會(huì)返回success饶号,否則就直接pop出第一個(gè)ID
    private static final String LUA_SCRIPT =
            "local key=KEYS[1]\nlocal initValue=tonumber(ARGV[1])\nlocal maxValue=tonumber(ARGV[2])\nlocal minCount=tonumber(ARGV[3])\nlocal initCount=tonumber(ARGV[4])\nlocal expansionStep=tonumber(ARGV[5])\nlocal incrStep=tonumber(ARGV[6])\nlocal len=redis.call('llen',key)\nlocal isInit=true\nlocal loop=initCount\nlocal nextValue=initValue\nif len>minCount\nthen\nreturn redis.call('lpop',key)\nend\nif len>0\nthen\nisInit=false\nloop=len+expansionStep\nnextValue=tonumber(redis.call('rpop',key))\nend\nwhile(len<loop)\ndo\nif nextValue>maxValue\nthen\nnextValue=initValue\nend\nredis.call('rpush',key,nextValue)\nnextValue=nextValue+incrStep\nlen=len+1\nend\nif isInit\nthen\nreturn 'success'\nend\nreturn redis.call('lpop',key)";
}

根據(jù)配置中的初始化字段的配置規(guī)則铁追,咱們還需要一個(gè)idInitFieldGenerator初始化字段生成器

@Bean("idInitFieldGenerator")
public SerialIdInitFieldGenerator serialIdInitFieldGenerator() {
    return new SerialIdInitFieldGenerator(idVariableGenerator());
}

public class SerialIdInitFieldGenerator extends InitFieldGenerator {

    private SerialIDVariableGenerator serialIDVariableGenerator;

    public SerialIdInitFieldGenerator(SerialIDVariableGenerator serialIDVariableGenerator) {
        this.serialIDVariableGenerator = serialIDVariableGenerator;
    }
        
    // 利用了SerialIDVariableGenerator變量生成器的方法初始化
    @Override
    public String generator(String key, String initField) {
        serialIDVariableGenerator.initParams(key, initField);
        return serialIDVariableGenerator.generate();
    }
}

總結(jié)

1.核心生成邏輯還是利用了redis執(zhí)行l(wèi)ua腳本的原子性

2.把表達(dá)式的生成邏輯拆分到具體的接口實(shí)現(xiàn)中去,方便規(guī)則的自定義擴(kuò)展

目前粗略測(cè)試下來(lái)茫船,線程并發(fā)的情況下大概1000個(gè)/s的生成速率琅束。還有比較大的優(yōu)化空間。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末透硝,一起剝皮案震驚了整個(gè)濱河市狰闪,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌濒生,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,539評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件幔欧,死亡現(xiàn)場(chǎng)離奇詭異罪治,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)礁蔗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門觉义,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人浴井,你說(shuō)我怎么就攤上這事晒骇。” “怎么了?”我有些...
    開封第一講書人閱讀 165,871評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵洪囤,是天一觀的道長(zhǎng)徒坡。 經(jīng)常有香客問(wèn)我,道長(zhǎng)瘤缩,這世上最難降的妖魔是什么喇完? 我笑而不...
    開封第一講書人閱讀 58,963評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮剥啤,結(jié)果婚禮上锦溪,老公的妹妹穿的比我還像新娘。我一直安慰自己府怯,他們只是感情好刻诊,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,984評(píng)論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著牺丙,像睡著了一般则涯。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上赘被,一...
    開封第一講書人閱讀 51,763評(píng)論 1 307
  • 那天是整,我揣著相機(jī)與錄音,去河邊找鬼民假。 笑死浮入,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的羊异。 我是一名探鬼主播事秀,決...
    沈念sama閱讀 40,468評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼野舶!你這毒婦竟也來(lái)了易迹?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤平道,失蹤者是張志新(化名)和其女友劉穎睹欲,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體一屋,經(jīng)...
    沈念sama閱讀 45,850評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡窘疮,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,002評(píng)論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了冀墨。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片闸衫。...
    茶點(diǎn)故事閱讀 40,144評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖诽嘉,靈堂內(nèi)的尸體忽然破棺而出蔚出,到底是詐尸還是另有隱情弟翘,我是刑警寧澤,帶...
    沈念sama閱讀 35,823評(píng)論 5 346
  • 正文 年R本政府宣布骄酗,位于F島的核電站稀余,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏酥筝。R本人自食惡果不足惜滚躯,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,483評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望嘿歌。 院中可真熱鬧掸掏,春花似錦、人聲如沸宙帝。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)步脓。三九已至愿待,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間靴患,已是汗流浹背仍侥。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留鸳君,地道東北人农渊。 一個(gè)月前我還...
    沈念sama閱讀 48,415評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像或颊,于是被迫代替她去往敵國(guó)和親砸紊。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,092評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容