Spring Boot 集成 Redisson分布式鎖(注解版)

Redisson 是一種基于 Redis 的 Java 駐留集群的分布式對(duì)象和服務(wù)庫(kù)嫂粟,可以為我們提供豐富的分布式鎖和線(xiàn)程安全集合的實(shí)現(xiàn)。在 Spring Boot 應(yīng)用程序中使用 Redisson 可以方便地實(shí)現(xiàn)分布式應(yīng)用程序的某些方面墨缘,例如分布式鎖星虹、分布式集合零抬、分布式事件發(fā)布和訂閱等。本篇是一個(gè)使用 Redisson 實(shí)現(xiàn)分布式鎖的詳細(xì)示例宽涌,在這個(gè)示例中平夜,我們定義了DistributedLock注解,它可以標(biāo)注在方法上卸亮,配合DistributedLockAspect切面以及IDistributedLock分布式鎖封裝的接口忽妒,來(lái)實(shí)現(xiàn)redisson 分布式鎖的 API 調(diào)用。

一兼贸、Spring Boot 集成 Redisson

1段直、在 pom.xml 文件中添加 Redisson 的相關(guān)依賴(lài)

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.3.0</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

<properties>
    <java.version>21</java.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.redisson</groupId>
        <artifactId>redisson-spring-boot-starter</artifactId>
        <version>3.30.0</version>
    </dependency>

    <!-- AspectJ依賴(lài) -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>

    <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>
</dependencies>

2、在 application.yml 中配置 Redisson單機(jī)模式 的連接信息和相關(guān)參數(shù)

spring:
  redis:
    host: localhost
    port: 6379
    password: null
redisson:
  codec: org.redisson.codec.JsonJacksonCodec
  threads: 4
  netty:
    threads: 4
  single-server-config:
    address: "redis://localhost:6379"
    password: null
    database: 0

3溶诞、Redission還支持主從坷牛、集群、哨兵配置


//主從模式
spring:
  redis:
    sentinel:
      master: my-master
      nodes: localhost:26379,localhost:26389
    password: your_password
redisson:
  master-slave-config:
    master-address: "redis://localhost:6379"
    slave-addresses: "redis://localhost:6380,redis://localhost:6381"
    password: ${spring.redis.password}
 
// 集群模式
spring:
  data:
    redis:
      cluster:
        nodes: localhost:6379,localhost:6380,localhost:6381,localhost:6382,localhost:6383,localhost:6384
      lettuce:
        pool:
          max-active: 10  # 允許最大連接數(shù)很澄,默認(rèn)8(負(fù)值表示沒(méi)有限制)京闰,推薦值:大于cpu * 2,通常為(cpu * 2) + 2
          max-idle: 8     # 最大空閑連接數(shù)甩苛,默認(rèn)8蹂楣,推薦值:cpu * 2
          min-idle: 0     # 最小空閑連接數(shù)痊土,默認(rèn)0
          max-wait: 5000    # 連接用完時(shí),新的請(qǐng)求等待時(shí)間(s秒、ms毫秒),超過(guò)該時(shí)間拋出異常官辈,默認(rèn)-1(負(fù)值表示沒(méi)有限制)
      timeout: 60000
      password:
redisson:
  cluster-config:
    node-addresses: "redis://localhost:6379,redis://localhost:6380,redis://localhost:6381,redis://localhost:6382,redis://localhost:6383,redis://localhost:6384"
    password: ${spring.redis.password}
 
// 哨兵模式
spring:
  redis:
    sentinel:
      master: my-master
      nodes: localhost:26379,localhost:26389
    password: your_password
redisson:
  sentinel-config:
    master-name: my-master
    sentinel-addresses: "redis://localhost:26379,redis://localhost:26380,redis://localhost:26381"
    password: ${spring.redis.password}

4风瘦、構(gòu)建Redission配置

@Component
@ConfigurationProperties(prefix = "spring.data.redis")
@Data
public class RedisConfigProperties {

    private Integer timeout;

    private String password;

    private cluster cluster;

    @Data
    public static class cluster {
        private String nodes;
    }
}
@Configuration
public class RedissonConfig {

    @Resource
    private RedisConfigProperties redisConfigProperties;

    //添加redisson的bean
    @Bean
    public Redisson redisson() {
        //redisson版本是3.5昧谊,集群的ip前面要加上“redis://”,不然會(huì)報(bào)錯(cuò)涡真,3.2版本可不加
        String[] nodes = redisConfigProperties.getCluster().getNodes().split(",");
        List<String> clusterNodes = new ArrayList<>(nodes.length);
        Arrays.stream(nodes).forEach((node) -> clusterNodes.add(
                node.startsWith("redis://") ? node : "redis://" + node)
        );
        Config config = new Config();
        ClusterServersConfig serverConfig = config.useClusterServers()
                .addNodeAddress(clusterNodes.toArray(new String[0]))
                .setTimeout(redisConfigProperties.getTimeout());
        if (StringUtils.hasText(redisConfigProperties.getPassword())) {
            serverConfig.setPassword(redisConfigProperties.getPassword());
        }
        return (Redisson) Redisson.create(config);
    }
}

二、本地封裝Redisson 分布式鎖

1东亦、定義ILock鎖對(duì)象

/**
 * @Author: huangyibo
 * @Date: 2024/5/26 2:12
 * @Description: RedissonLock 包裝的鎖對(duì)象 實(shí)現(xiàn)AutoCloseable接口橄登,在java7的try(with resource)語(yǔ)法,不用顯示調(diào)用close方法
 */

@AllArgsConstructor
public class ILock implements AutoCloseable {

    /**
     * 持有的鎖對(duì)象
     */
    @Getter
    private Object lock;

    /**
     * 分布式鎖接口
     */
    @Getter
    private IDistributedLock distributedLock;

    @Override
    public void close() throws Exception {
        if(Objects.nonNull(lock)){
            distributedLock.unLock(lock);
        }
    }
}

2讥此、定義IDistributedLock分布式鎖接口

public interface IDistributedLock {
    
    /**
     * 獲取鎖拢锹,默認(rèn)30秒失效,失敗一直等待直到獲取鎖
     *
     * @param key 鎖的key
     * @return 鎖對(duì)象
     */
    ILock lock(String key);

    /**
     * 獲取鎖,失敗一直等待直到獲取鎖
     *
     * @param key      鎖的key
     * @param lockTime 加鎖的時(shí)間萄喳,超過(guò)這個(gè)時(shí)間后鎖便自動(dòng)解鎖卒稳; 如果lockTime為-1,則保持鎖定直到顯式解鎖
     * @param unit     {@code lockTime} 參數(shù)的時(shí)間單位
     * @param fair     是否公平鎖
     * @return 鎖對(duì)象
     */
    ILock lock(String key, long lockTime, TimeUnit unit, boolean fair);

    /**
     * 嘗試獲取鎖他巨,30秒獲取不到超時(shí)異常充坑,鎖默認(rèn)30秒失效
     *
     * @param key     鎖的key
     * @param tryTime 獲取鎖的最大嘗試時(shí)間
     * @return
     * @throws Exception
     */
    ILock tryLock(String key, long tryTime) throws Exception;

    /**
     * 嘗試獲取鎖减江,獲取不到超時(shí)異常
     *
     * @param key      鎖的key
     * @param tryTime  獲取鎖的最大嘗試時(shí)間
     * @param lockTime 加鎖的時(shí)間
     * @param unit     {@code tryTime @code lockTime} 參數(shù)的時(shí)間單位
     * @param fair     是否公平鎖
     * @return
     * @throws Exception
     */
    ILock tryLock(String key, long tryTime, long lockTime, TimeUnit unit, boolean fair) throws Exception;

    /**
     * 解鎖
     *
     * @param lock
     * @throws Exception
     */
    void unLock(Object lock);
    
    /**
     * 釋放鎖
     *
     * @param lock
     * @throws Exception
     */
    default void unLock(ILock lock) {
        if (lock != null) {
            unLock(lock.getLock());
        }
    }
    
}

3、IDistributedLock實(shí)現(xiàn)類(lèi)

@Slf4j
@Component
public class RedissonDistributedLock implements IDistributedLock {

    @Resource
    private RedissonClient redissonClient;
    /**
     * 統(tǒng)一前綴
     */
    @Value("${redisson.lock.prefix:bi:distributed:lock}")
    private String prefix;

    @Override
    public ILock lock(String key) {
        return this.lock(key, 0L, TimeUnit.SECONDS, false);
    }

    @Override
    public ILock lock(String key, long lockTime, TimeUnit unit, boolean fair) {
        RLock lock = getLock(key, fair);
        // 獲取鎖,失敗一直等待,直到獲取鎖,不支持自動(dòng)續(xù)期
        if (lockTime > 0L) {
            lock.lock(lockTime, unit);
        } else {
            // 具有Watch Dog 自動(dòng)延期機(jī)制 默認(rèn)續(xù)30s 每隔30/3=10 秒續(xù)到30s
            lock.lock();
        }
        return new ILock(lock, this);
    }

    @Override
    public ILock tryLock(String key, long tryTime) throws Exception {
        return this.tryLock(key, tryTime, 0L, TimeUnit.SECONDS, false);
    }

    @Override
    public ILock tryLock(String key, long tryTime, long lockTime, TimeUnit unit, boolean fair)
            throws Exception {
        RLock lock = getLock(key, fair);
        boolean lockAcquired;
        // 嘗試獲取鎖捻爷,獲取不到超時(shí)異常,不支持自動(dòng)續(xù)期
        if (lockTime > 0L) {
            lockAcquired = lock.tryLock(tryTime, lockTime, unit);
        } else {
            // 具有Watch Dog 自動(dòng)延期機(jī)制 默認(rèn)續(xù)30s 每隔30/3=10 秒續(xù)到30s
            lockAcquired = lock.tryLock(tryTime, unit);
        }
        if (lockAcquired) {
            return new ILock(lock, this);
        }
        return null;
    }

    /**
     * 獲取鎖
     *
     * @param key
     * @param fair
     * @return
     */
    private RLock getLock(String key, boolean fair) {
        RLock lock;
        String lockKey = prefix + ":" + key;
        if (fair) {
            // 獲取公平鎖
            lock = redissonClient.getFairLock(lockKey);
        } else {
            // 獲取普通鎖
            lock = redissonClient.getLock(lockKey);
        }
        return lock;
    }

    @Override
    public void unLock(Object lock) {
        if (!(lock instanceof RLock)) {
            throw new IllegalArgumentException("Invalid lock object");
        }
        RLock rLock = (RLock) lock;
        if (rLock.isLocked()) {
            try {
                rLock.unlock();
            } catch (IllegalMonitorStateException e) {
                log.error("釋放分布式鎖異常", e);
            }
        }
    }
}

4辈灼、注入IDistributedLock接口使用示例

public interface IProductSkuSupplierMeasureService {

    /**
     * 保存SKU供應(yīng)商供貨信息
     *
     * @param dto
     * @return
     */
    Boolean saveSupplierInfo(ProductSkuSupplierInfoDTO dto);

    /**
     * 編輯SKU供應(yīng)商供貨信息
     *
     * @param dto
     * @return
     */
    Boolean editSupplierInfo(ProductSkuSupplierInfoDTO dto);
}

手動(dòng)釋放鎖示例

@Service
@Slf4j
public class ProductSkuSupplierMeasureServiceImpl implements IProductSkuSupplierMeasureService {

    @Resource
    private IDistributedLock distributedLock;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean saveSupplierInfo(ProductSkuSupplierInfoDTO dto) {
        // 手動(dòng)釋放鎖
        String sku = dto.getSku();
        ILock lock = null;
        try {
            lock = distributedLock.lock(dto.getSku(),10L, TimeUnit.SECONDS, false);
            if (Objects.isNull(lock)) {
                throw new BusinessException("Duplicate request for method still in process");
            }
            // 業(yè)務(wù)代碼
        }catch (BusinessException e) {
            throw new BusinessException(e.getMessage());
        } catch (Exception e) {
            log.error("保存異常", e);
            throw new BusinessException (e.getMessage());
        } finally {
            if (Objects.nonNull(lock)) {
                distributedLock.unLock(lock);
            }
        }
        return Boolean.TRUE;
    }
}

使用try-with-resources 語(yǔ)法糖自動(dòng)釋放鎖

@Service
@Slf4j
public class ProductSkuSupplierMeasureServiceImpl implements IProductSkuSupplierMeasureService {

    @Resource
    private IDistributedLock distributedLock;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean editSupplierInfo(ProductSkuSupplierInfoDTO dto) {

        String sku = dto.getSku();
        // try-with-resources 語(yǔ)法糖自動(dòng)釋放鎖
        try(ILock lock = distributedLock.lock(dto.getSku(),10L, TimeUnit.SECONDS, false)) {
            if(Objects.isNull(lock)){
                throw new BusinessException ("Duplicate request for method still in process");
            }

            // 業(yè)務(wù)代碼
        }catch (BusinessException e) {
            throw new BusinessException (e.getMessage());
        } catch (Exception e) {
            log.error("修改異常", e);
            throw new BusinessException ("修改異常");
        }
        return Boolean.TRUE;
    }
}

5、使用AOP切面實(shí)現(xiàn)分布式鎖的綁定

定義DistributedLock注解

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DistributedLock {

    /**
     * 保證業(yè)務(wù)接口的key的唯一性也榄,否則失去了分布式鎖的意義 鎖key
     * 支持使用spEl表達(dá)式
     */
    String key();

    /**
     * 保證業(yè)務(wù)接口的key的唯一性巡莹,否則失去了分布式鎖的意義 鎖key 前綴
     */
    String keyPrefix() default "";

    /**
     * 是否在等待時(shí)間內(nèi)獲取鎖,如果在等待時(shí)間內(nèi)無(wú)法獲取到鎖甜紫,則返回失敗
     */
    boolean tryLok() default false;

    /**
     * 獲取鎖的最大嘗試時(shí)間 降宅,會(huì)嘗試tryTime時(shí)間獲取鎖,在該時(shí)間內(nèi)獲取成功則返回囚霸,否則拋出獲取鎖超時(shí)異常腰根,tryLok=true時(shí),該值必須大于0拓型。
     *
     */
    long tryTime() default 0;

    /**
     * 加鎖的時(shí)間额嘿,超過(guò)這個(gè)時(shí)間后鎖便自動(dòng)解鎖
     */
    long lockTime() default 30;

    /**
     * tryTime 和 lockTime的時(shí)間單位
     */
    TimeUnit unit() default TimeUnit.SECONDS;

    /**
     * 是否公平鎖,false:非公平鎖劣挫,true:公平鎖
     */
    boolean fair() default false;
}

定義DistributedLockAspect Lock切面

@Aspect
@Slf4j
public class DistributedLockAspect {

    @Resource
    private IDistributedLock distributedLock;

    /**
     * SpEL表達(dá)式解析
     */
    private SpelExpressionParser spelExpressionParser = new SpelExpressionParser();

    /**
     * 用于獲取方法參數(shù)名字
     */
    private DefaultParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();

    @Pointcut("@annotation(com.yibo.redissonannodemo.anno.DistributedLock)")
    public void distributorLock() {

    }

    @Around("distributorLock()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        // 獲取DistributedLock
        DistributedLock distributedLock = this.getDistributedLock(pjp);
        // 獲取 lockKey
        String lockKey = this.getLockKey(pjp, distributedLock);
        ILock lockObj = null;
        try {
            // 加鎖册养,tryLok = true,并且tryTime > 0時(shí),嘗試獲取鎖揣云,獲取不到超時(shí)異常
            if (distributedLock.tryLok()) {
                if(distributedLock.tryTime() <= 0){
                    throw new RuntimeException("tryTime must be greater than 0");
                }
                lockObj = this.distributedLock.tryLock(lockKey, distributedLock.tryTime(), distributedLock.lockTime(), distributedLock.unit(), distributedLock.fair());
            } else {
                lockObj = this.distributedLock.lock(lockKey, distributedLock.lockTime(), distributedLock.unit(), distributedLock.fair());
            }

            if (Objects.isNull(lockObj)) {
                throw new RuntimeException("Duplicate request for method still in process");
            }
            return pjp.proceed();
        } catch (Exception e) {
            throw e;
        } finally {
            // 解鎖
            this.unLock(lockObj);
        }
    }

    /**
     * @param pjp
     * @return
     * @throws NoSuchMethodException
     */
    private DistributedLock getDistributedLock(ProceedingJoinPoint pjp) throws NoSuchMethodException {
        String methodName = pjp.getSignature().getName();
        Class clazz = pjp.getTarget().getClass();
        Class<?>[] par = ((MethodSignature) pjp.getSignature()).getParameterTypes();
        Method lockMethod = clazz.getMethod(methodName, par);
        DistributedLock distributedLock = lockMethod.getAnnotation(DistributedLock.class);
        return distributedLock;
    }

    /**
     * 解鎖
     *
     * @param lockObj
     */
    private void unLock(ILock lockObj) {
        if (Objects.isNull(lockObj)) {
            return;
        }
        try {
            this.distributedLock.unLock(lockObj);
        } catch (Exception e) {
            log.error("分布式鎖解鎖異常", e);
        }
    }

    /**
     * 獲取 lockKey
     *
     * @param pjp
     * @param distributedLock
     * @return
     */
    private String getLockKey(ProceedingJoinPoint pjp, DistributedLock distributedLock) {
        String lockKey = distributedLock.key();
        String keyPrefix = distributedLock.keyPrefix();
        if (StringUtils.hasText(lockKey)) {
            throw new RuntimeException("Lok key cannot be empty");
        }
        if (lockKey.contains("#")) {
            this.checkSpEL(lockKey);
            MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
            // 獲取方法參數(shù)值
            Object[] args = pjp.getArgs();
            lockKey = getValBySpEL(lockKey, methodSignature, args);
        }
        lockKey = StringUtils.hasText(keyPrefix) ? lockKey : keyPrefix + lockKey;
        return lockKey;
    }

    /**
     * 解析spEL表達(dá)式
     *
     * @param spEL
     * @param methodSignature
     * @param args
     * @return
     */
    private String getValBySpEL(String spEL, MethodSignature methodSignature, Object[] args) {
        // 獲取方法形參名數(shù)組
        String[] paramNames = nameDiscoverer.getParameterNames(methodSignature.getMethod());
        if (paramNames == null || paramNames.length < 1) {
            throw new RuntimeException("Lok key cannot be empty");
        }
        Expression expression = spelExpressionParser.parseExpression(spEL);
        // spring的表達(dá)式上下文對(duì)象
        EvaluationContext context = new StandardEvaluationContext();
        // 給上下文賦值
        for (int i = 0; i < args.length; i++) {
            context.setVariable(paramNames[i], args[i]);
        }
        Object value = expression.getValue(context);
        if (value == null) {
            throw new RuntimeException("The parameter value cannot be null");
        }
        return value.toString();
    }

    /**
     * SpEL 表達(dá)式校驗(yàn)
     *
     * @param spEL
     * @return
     */
    private void checkSpEL(String spEL) {
        try {
            ExpressionParser parser = new SpelExpressionParser();
            parser.parseExpression(spEL, new TemplateParserContext());
        } catch (Exception e) {
            log.error("spEL表達(dá)式解析異常", e);
            throw new RuntimeException("Invalid SpEL expression [" + spEL + "]");
        }
    }
}

定義分布式鎖注解版啟動(dòng)元注解

/**
 * @Author: huangyibo
 * @Date: 2024/5/26 2:34
 * @Description: 定義分布式鎖注解版啟動(dòng)元注解
 */

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({DistributedLockAspect.class})
public @interface EnableDistributedLock {

}

6捕儒、AOP切面實(shí)現(xiàn)分布式鎖的綁定使用示例

啟動(dòng)類(lèi)添加@EnableDistributedLock啟用注解支持

@SpringBootApplication
@EnableDistributedLock
public class RedissonAnnoDemoApplication {

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

}

@DistributedLock標(biāo)注需要使用分布式鎖的方法

@Service
@Slf4j
public class ProductSkuSupplierMeasureServiceImpl implements IProductSkuSupplierMeasureService {

    //@DistributedLock(key = "#dto.sku + '-' + #dto.skuId", lockTime = 10L, keyPrefix = "sku-")
    @DistributedLock(key = "#dto.sku", lockTime = 10L, keyPrefix = "sku-")
    @Override
    public Boolean addSupplierOrder(ProductSkuSupplierInfoDTO dto) {

        //執(zhí)行創(chuàng)建供應(yīng)商訂單業(yè)務(wù)

        return Boolean.TRUE;
    }

}

參考:
https://blog.csdn.net/Ascend1977/article/details/131126047

https://blog.csdn.net/m0_55712478/article/details/135300831

https://www.cnblogs.com/youcong/p/13939485.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市邓夕,隨后出現(xiàn)的幾起案子刘莹,更是在濱河造成了極大的恐慌,老刑警劉巖焚刚,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件点弯,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡矿咕,警方通過(guò)查閱死者的電腦和手機(jī)抢肛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)碳柱,“玉大人捡絮,你說(shuō)我怎么就攤上這事×停” “怎么了福稳?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀(guān)的道長(zhǎng)瑞侮。 經(jīng)常有香客問(wèn)我的圆,道長(zhǎng)鼓拧,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任越妈,我火速辦了婚禮季俩,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘梅掠。我一直安慰自己酌住,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布瓤檐。 她就那樣靜靜地躺著赂韵,像睡著了一般娱节。 火紅的嫁衣襯著肌膚如雪挠蛉。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 48,970評(píng)論 1 284
  • 那天肄满,我揣著相機(jī)與錄音谴古,去河邊找鬼。 笑死稠歉,一個(gè)胖子當(dāng)著我的面吹牛掰担,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播怒炸,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼带饱,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了阅羹?” 一聲冷哼從身側(cè)響起勺疼,我...
    開(kāi)封第一講書(shū)人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎捏鱼,沒(méi)想到半個(gè)月后执庐,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡导梆,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年轨淌,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片看尼。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡递鹉,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出藏斩,到底是詐尸還是另有隱情躏结,我是刑警寧澤,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布灾茁,位于F島的核電站窜觉,受9級(jí)特大地震影響谷炸,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜禀挫,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一旬陡、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧语婴,春花似錦描孟、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至缠导,卻和暖如春廉羔,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背僻造。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工憋他, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人髓削。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓竹挡,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親立膛。 傳聞我的和親對(duì)象是個(gè)殘疾皇子揪罕,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

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