SpringCloud微服務(wù)實(shí)戰(zhàn)——搭建企業(yè)級開發(fā)框架(四十四):【微服務(wù)監(jiān)控告警實(shí)現(xiàn)方式一】使用Actuator + Spring Boot Admin實(shí)現(xiàn)簡單的微服務(wù)監(jiān)控告警系統(tǒng)

??業(yè)務(wù)系統(tǒng)正常運(yùn)行的穩(wěn)定性十分重要击罪,作為SpringBoot的四大核心之一芋簿,Actuator讓你時(shí)刻探知SpringBoot服務(wù)運(yùn)行狀態(tài)信息,是保障系統(tǒng)正常運(yùn)行必不可少的組件说墨。
??spring-boot-starter-actuator提供的是一系列HTTP或者JMX監(jiān)控端點(diǎn)骏全,通過監(jiān)控端點(diǎn)我們可以獲取到系統(tǒng)的運(yùn)行統(tǒng)計(jì)信息,同時(shí)婉刀,我們可以自己選擇開啟需要的監(jiān)控端點(diǎn)吟温,也可以自定義擴(kuò)展監(jiān)控端點(diǎn)。
??Actuator通過端點(diǎn)對外暴露的監(jiān)控信息是JSON格式數(shù)據(jù)突颊,我們需要使用界面來展示鲁豪,目前使用比較多的就是Spring Boot Admin或者Prometheus + Grafana的方式:Spring Boot Admin實(shí)現(xiàn)起來相對比較簡單潘悼,不存在數(shù)據(jù)庫,不能存儲(chǔ)和展示歷史監(jiān)控?cái)?shù)據(jù)爬橡;Prometheus(時(shí)序數(shù)據(jù)庫) + Grafana(界面)的方式相比較而言功能更豐富治唤,提供歷史記錄存儲(chǔ),界面展示也比較美觀糙申。
??相比較而言宾添,Prometheus + Grafana的方式更為流行一些,現(xiàn)在的微服務(wù)及Kubernetes基本是采用這種方式的柜裸。但是對于小的項(xiàng)目或者單體應(yīng)用缕陕,Spring Boot Admin會(huì)更加方便快捷一些。具體采用那種方式疙挺,可以根據(jù)自己的系統(tǒng)運(yùn)維需求來取舍扛邑,這里我們把框架集成兩種方式,在實(shí)際應(yīng)用過程中自有選擇铐然。

??本文主要介紹如何集成Spring Boot Admin以及通過SpringSecurity控制Actuator的端點(diǎn)權(quán)限蔬崩。

1、在基礎(chǔ)服務(wù)gitegg-platform中引入spring-boot-starter-actuator包搀暑。

??無論是使用Spring Boot Admin還是使用Prometheus + Grafana的方式都需要spring-boot-starter-actuator來獲取監(jiān)控信息沥阳,這里將spring-boot-starter-actuator包添加到gitegg-platform-boot基礎(chǔ)平臺(tái)包中,這樣所有的微服務(wù)都集成了此功能自点。

        <!-- spring boot 健康監(jiān)控 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
2桐罕、確定并引入工程使用的spring-boot-admin-starter-server和spring-boot-admin-starter-client依賴包。

??spring-boot-admin-starter-server是Spring Boot Admin的服務(wù)端樟氢,我們需要新建一個(gè)SpringBoot工程來啟動(dòng)這個(gè)服務(wù)端冈绊,用來接收需要監(jiān)控的服務(wù)注冊,展示監(jiān)控告警信息埠啃。spring-boot-admin-starter-client是客戶端,需要被監(jiān)控的服務(wù)需要引入這個(gè)依賴包伟恶。
??此處請注意: 看到網(wǎng)上很多文章里面寫著添加spring-boot-admin-starter-client包碴开,在SpringCloud微服務(wù)中是不需要引入的,spring-boot-admin-starter-client包僅僅是為了引入我們gitegg-platform平臺(tái)工程的對應(yīng)版本博秫,在gitegg-boot框架中使用潦牛,在SpringCloud微服務(wù)框架中,不需要引入spring-boot-admin-starter-client挡育,SpringBootAdmin會(huì)自動(dòng)根據(jù)微服務(wù)注冊信息查找服務(wù)端點(diǎn)巴碗,官方文檔說明:spring-cloud-discovery-support
??在選擇版本時(shí)即寒,一定要找到對應(yīng)SpringBoot版本的Spring Boot Admin橡淆,GitHub上有版本對應(yīng)關(guān)系的說明:

版本對應(yīng)關(guān)系

??我們在gitegg-platform-pom中來定義需要引入的spring-boot-admin-starter-server和spring-boot-admin-starter-client依賴包版本召噩,然后在微服務(wù)業(yè)務(wù)開發(fā)中具體引入,這里不做統(tǒng)一引入逸爵,方便微服務(wù)切換監(jiān)控方式具滴。

......
        <!-- spring-boot-admin 微服務(wù)監(jiān)控-->
        <spring.boot.admin.version>2.3.1</spring.boot.admin.version>
......
            <!-- spring-boot-admin監(jiān)控 服務(wù)端 https://mvnrepository.com/artifact/de.codecentric/spring-boot-admin-starter-server -->
            <dependency>
                <groupId>de.codecentric</groupId>
                <artifactId>spring-boot-admin-starter-server</artifactId>
                <version>${spring.boot.admin.version}</version>
            </dependency>

            <!-- spring-boot-admin監(jiān)控 客戶端 https://mvnrepository.com/artifact/de.codecentric/spring-boot-admin-starter-client -->
            <dependency>
                <groupId>de.codecentric</groupId>
                <artifactId>spring-boot-admin-starter-client</artifactId>
                <version>${spring.boot.admin.version}</version>
            </dependency>.
......
3、在GitEgg-Cloud項(xiàng)目的gitegg-plugin工程下新建gitegg-admin-monitor工程师倔,用于運(yùn)行spring-boot-admin-starter-server构韵。
  • pom.xml中引入需要的依賴包
    <dependencies>
        <!-- gitegg Spring Boot自定義及擴(kuò)展 -->
        <dependency>
            <groupId>com.gitegg.platform</groupId>
            <artifactId>gitegg-platform-boot</artifactId>
            <!-- 去除gitegg-platform-boot默認(rèn)的依賴-->
            <exclusions>
                <exclusion>
                    <groupId>com.gitegg.platform</groupId>
                    <artifactId>gitegg-platform-cache</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!-- gitegg Spring Cloud自定義及擴(kuò)展 -->
        <dependency>
            <groupId>com.gitegg.platform</groupId>
            <artifactId>gitegg-platform-cloud</artifactId>
        </dependency>
        <!-- security -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
            <!-- 去除springboot默認(rèn)的logback配置-->
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>de.codecentric</groupId>
            <artifactId>spring-boot-admin-starter-server</artifactId>
        </dependency>
    </dependencies>

  • 添加spring-boot-admin-starter-server啟動(dòng)類GitEggMonitorApplication.java,添加@EnableAdminServer注解即可趋艘。
@EnableAdminServer
@SpringBootApplication
@RefreshScope
public class GitEggMonitorApplication {
    
    public static void main(String[] args)
    {
        SpringApplication.run(GitEggMonitorApplication.class, args);
    }
    
}
  • 添加SpringSecurity的WebSecurityConfigurerAdapter配置類疲恢,保護(hù)監(jiān)控系統(tǒng)安全。
    ??這里主要配置登錄頁面瓷胧、靜態(tài)文件冈闭、登錄、退出等的權(quán)限抖单。請注意這里配置了publicUrl的前綴萎攒,當(dāng)部署在微服務(wù)環(huán)境或Docker環(huán)境中需要經(jīng)過gateway或者nginx轉(zhuǎn)發(fā)時(shí),在SpringBootAdmin配置中矛绘,需要配置publicUrl耍休,否則SpringBootAdmin只會(huì)跳轉(zhuǎn)到本機(jī)環(huán)境的地址和端口。publicUrl如果是80端口货矮,那么這個(gè)端口不能省略羊精,需要配置上。
@Configuration(proxyBeanMethods = false)
public class SecuritySecureConfig extends WebSecurityConfigurerAdapter {
    
    private final AdminServerUiProperties adminUi;
    
    private final AdminServerProperties adminServer;
    
    private final SecurityProperties security;
    
    public SecuritySecureConfig(AdminServerUiProperties adminUi, AdminServerProperties adminServer, SecurityProperties security) {
        this.adminUi = adminUi;
        this.adminServer = adminServer;
        this.security = security;
    }
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        
        // 當(dāng)設(shè)置了publicUrl時(shí)囚玫,Gateway跳轉(zhuǎn)到login或logout鏈接需要redirect到publicUrl
        String publicUrl = this.adminUi.getPublicUrl() != null ? this.adminUi.getPublicUrl() : this.adminServer.getContextPath();
        SavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
        successHandler.setTargetUrlParameter("redirectTo");
        successHandler.setDefaultTargetUrl(publicUrl + "/");
        
        http.authorizeRequests(
                (authorizeRequests) -> authorizeRequests.antMatchers(this.adminServer.path("/assets/**")).permitAll()
                        .antMatchers(this.adminServer.path("/actuator/info")).permitAll()
                        .antMatchers(this.adminServer.path("/actuator/health")).permitAll()
                        .antMatchers(this.adminServer.path("/login")).permitAll().anyRequest().authenticated()
        ).formLogin(
                (formLogin) -> formLogin.loginPage(publicUrl + "/login").loginProcessingUrl(this.adminServer.path("/login")).successHandler(successHandler).and()
        ).logout((logout) -> logout.logoutUrl(publicUrl + "/logout")).httpBasic(Customizer.withDefaults())
                .csrf((csrf) -> csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
                        .ignoringRequestMatchers(
                                new AntPathRequestMatcher(this.adminServer.path("/instances"),
                                        HttpMethod.POST.toString()),
                                new AntPathRequestMatcher(this.adminServer.path("/instances/*"),
                                        HttpMethod.DELETE.toString()),
                                new AntPathRequestMatcher(this.adminServer.path("/actuator/**"))
                        ))
                .rememberMe((rememberMe) -> rememberMe.key(UUID.randomUUID().toString()).tokenValiditySeconds(1209600));
    }
    
    /**
     * Required to provide UserDetailsService for "remember functionality"
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().withUser(security.getUser().getName())
                .password("{noop}" + security.getUser().getPassword()).roles(security.getUser().getRoles().toArray(new String[0]));
    }
    
}
4喧锦、在Nacos配置中心配置SpringBootAdmin的相關(guān)配置,在gitegg-admin-monitor工程中抓督,也需要配置讀取配置的相關(guān)yml文件燃少,除了讀取主配置之外,還需要讀取SpringBootAdmin專屬配置铃在。
  • 新增gitegg-cloud-config-admin-monitor.yaml配置文件
spring:
  boot:
    admin:
      ui:
        brand: <img src="http://img.gitegg.com/cloud/docs/images/logo.png"><span>GitEgg微服務(wù)監(jiān)控系統(tǒng)</span>
        title: GitEgg微服務(wù)監(jiān)控系統(tǒng)
        favicon: http://img.gitegg.com/cloud/docs/images/logo.png
        public-url: http://127.0.0.1:80/gitegg-admin-monitor/monitor
      context-path: /monitor
  • 在bootstrap.yml中新增讀取gitegg-cloud-config-admin-monitor.yaml的配置
server:
  port: 8009
spring:
  profiles:
    active: '@spring.profiles.active@'
  application:
    name: '@artifactId@'
  cloud:
    inetutils:
      ignored-interfaces: docker0
    nacos:
      discovery:
        server-addr: ${spring.nacos.addr}
        metadata:
          # 啟用SpringBootAdmin時(shí) 客戶端端點(diǎn)信息的安全認(rèn)證信息
          user.name: ${spring.security.user.name}
          user.password: ${spring.security.user.password}
      config:
        server-addr: ${spring.nacos.addr}
        file-extension: yaml
        extension-configs:
          # 必須帶文件擴(kuò)展名阵具,此時(shí) file-extension 的配置對自定義擴(kuò)展配置的 Data Id 文件擴(kuò)展名沒有影響
          - data-id: ${spring.nacos.config.prefix}.yaml
            group: ${spring.nacos.config.group}
            refresh: true
          - data-id: ${spring.nacos.config.prefix}-admin-monitor.yaml
            group: ${spring.nacos.config.group}
            refresh: true
5、擴(kuò)展gitegg-gateway的SpringSecurity配置定铜,增加統(tǒng)一鑒權(quán)校驗(yàn)阳液。因我們有多個(gè)微服務(wù),且所有的微服務(wù)在生產(chǎn)環(huán)境部署時(shí)都不會(huì)暴露端口揣炕,所以所有的微服務(wù)鑒權(quán)都會(huì)在網(wǎng)關(guān)做帘皿。

??SpringSecurity權(quán)限驗(yàn)證支持多過濾器配置,同時(shí)可配置驗(yàn)證順序畸陡,我們這里需要改造之前的過濾器鹰溜,這里新增Basic認(rèn)證過濾器虽填,通過securityMatcher設(shè)置,只有健康檢查的請求走這個(gè)權(quán)限過濾器奉狈,其他請求繼續(xù)走之前我們設(shè)置的OAuth2+JWT權(quán)限驗(yàn)證器卤唉。

/**
 * 權(quán)限配置
 * 注解需要使用@EnableWebFluxSecurity而非@EnableWebSecurity,因?yàn)镾pringCloud Gateway基于WebFlux
 *
 * @author GitEgg
 *
 */
@RequiredArgsConstructor(onConstructor_ = @Autowired)
@Configuration
@EnableWebFluxSecurity
public class MultiWebSecurityConfig {
    
    private final AuthorizationManager authorizationManager;
    
    private final AuthServerAccessDeniedHandler authServerAccessDeniedHandler;
    
    private final AuthServerAuthenticationEntryPoint authServerAuthenticationEntryPoint;
    
    private final AuthUrlWhiteListProperties authUrlWhiteListProperties;
    
    private final WhiteListRemoveJwtFilter whiteListRemoveJwtFilter;
    
    private final SecurityProperties securityProperties;
    
    @Value("${management.endpoints.web.base-path:}")
    private String actuatorPath;
    
    /**
     * 健康檢查接口權(quán)限配置
     * @param http
     * @return
     */
    @Order(Ordered.HIGHEST_PRECEDENCE)
    @Bean
    @ConditionalOnProperty( value = {"management.security.enabled", "management.endpoints.enabled-by-default"}, havingValue = "true")
    SecurityWebFilterChain webHttpSecurity(ServerHttpSecurity http) {
        if (StringUtils.isEmpty(actuatorPath))
        {
            throw new BusinessException("當(dāng)啟用健康檢查時(shí),不允許健康檢查的路徑為空");
        }
        http
                .cors()
                .and()
                .csrf().disable()
                .formLogin().disable()
                .securityMatcher(new OrServerWebExchangeMatcher(
                        new PathPatternParserServerWebExchangeMatcher(actuatorPath + "/**"),
                        new PathPatternParserServerWebExchangeMatcher("/**" + actuatorPath + "/**")
                ))
                .authorizeExchange((exchanges) -> exchanges
                        .anyExchange().hasAnyRole(securityProperties.getUser().getRoles().toArray(new String[0]))
                )
                .httpBasic(Customizer.withDefaults());
        return http.build();
    }
    
    /**
     * 設(shè)置Basic認(rèn)證用戶信息
     * @return
     */
    @Bean
    @ConditionalOnProperty( value = {"management.security.enabled", "management.endpoints.enabled-by-default"}, havingValue = "true")
    ReactiveUserDetailsService userDetailsService() {
        return new MapReactiveUserDetailsService(User
                .withUsername(securityProperties.getUser().getName())
                .password(passwordEncoder().encode(securityProperties.getUser().getPassword()))
                .roles(securityProperties.getUser().getRoles().toArray(new String[0]))
                .build());
    }
    
    /**
     * 設(shè)置密碼編碼
     * @return
     */
    @Bean
    @ConditionalOnProperty( value = {"management.security.enabled", "management.endpoints.enabled-by-default"}, havingValue = "true")
    public static PasswordEncoder passwordEncoder() {
        DelegatingPasswordEncoder delegatingPasswordEncoder =
                (DelegatingPasswordEncoder) PasswordEncoderFactories.createDelegatingPasswordEncoder();
        return  delegatingPasswordEncoder;
    }
    
    /**
     * 路由轉(zhuǎn)發(fā)權(quán)限配置
     * @param http
     * @return
     */
    @Bean
    SecurityWebFilterChain apiHttpSecurity(ServerHttpSecurity http) {
        
        http.oauth2ResourceServer().jwt()
                .jwtAuthenticationConverter(jwtAuthenticationConverter());
    
        // 自定義處理JWT請求頭過期或簽名錯(cuò)誤的結(jié)果
        http.oauth2ResourceServer().authenticationEntryPoint(authServerAuthenticationEntryPoint);
    
        // 對白名單路徑仁期,直接移除JWT請求頭桑驱,不移除的話,后臺(tái)會(huì)校驗(yàn)jwt
        http.addFilterBefore(whiteListRemoveJwtFilter, SecurityWebFiltersOrder.AUTHENTICATION);
    
        // Basic認(rèn)證直接放行
        if (!CollectionUtils.isEmpty(authUrlWhiteListProperties.getTokenUrls()))
        {
            http.authorizeExchange().pathMatchers(ArrayUtil.toArray(authUrlWhiteListProperties.getTokenUrls(), String.class)).permitAll();
        }
    
        // 判斷是否有靜態(tài)文件
        if (!CollectionUtils.isEmpty(authUrlWhiteListProperties.getStaticFiles()))
        {
            http.authorizeExchange().pathMatchers(ArrayUtil.toArray(authUrlWhiteListProperties.getStaticFiles(), String.class)).permitAll();
        }
    
        http.authorizeExchange()
                .pathMatchers(ArrayUtil.toArray(authUrlWhiteListProperties.getWhiteUrls(), String.class)).permitAll()
                .anyExchange().access(authorizationManager)
                .and()
                .exceptionHandling()
                /**
                 * 處理未授權(quán)
                 */
                .accessDeniedHandler(authServerAccessDeniedHandler)
                /**
                 * 處理未認(rèn)證
                 */
                .authenticationEntryPoint(authServerAuthenticationEntryPoint)
                .and()
                .cors()
                .and().csrf().disable();
    
        return http.build();
    }
    
    /**
     * ServerHttpSecurity沒有將jwt中authorities的負(fù)載部分當(dāng)做Authentication跛蛋,需要把jwt的Claim中的authorities加入
     * 解決方案:重新定義ReactiveAuthenticationManager權(quán)限管理器熬的,默認(rèn)轉(zhuǎn)換器JwtGrantedAuthoritiesConverter
     */
    @Bean
    public Converter<Jwt, ? extends Mono<? extends AbstractAuthenticationToken>> jwtAuthenticationConverter() {
        JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
        jwtGrantedAuthoritiesConverter.setAuthorityPrefix(AuthConstant.AUTHORITY_PREFIX);
        jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName(AuthConstant.AUTHORITY_CLAIM_NAME);
        
        JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
        jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);
        return new ReactiveJwtAuthenticationConverterAdapter(jwtAuthenticationConverter);
    }
}

6、在Nacos配置中心赊级,統(tǒng)一配置所有微服務(wù)的健康檢查端點(diǎn)地址押框,權(quán)限校驗(yàn)的用戶名密碼等。
spring:
......
  security:
    # # 啟用SpringBootAdmin時(shí)理逊,健康檢查權(quán)限校驗(yàn)橡伞,不使用SpringBootAdmin此處可省略
    user:
      name: user
      password: password
      roles: ACTUATOR_ADMIN
......

# 性能監(jiān)控端點(diǎn)配置
management:
  security:
    enabled: true
    role: ACTUATOR_ADMIN
  endpoint:
    health:
      show-details: always
  endpoints:
    enabled-by-default: true
    web:
      base-path: /actuator
      exposure:
        include: '*'
  server:
    servlet:
      context-path: /actuator
  health:
    mail:
      enabled: false
......
7、設(shè)置網(wǎng)關(guān)Gateway配置晋被,對gitegg-admin-monitor進(jìn)行過路由和轉(zhuǎn)發(fā)兑徘。
spring:
    gateway:
      discovery:
        locator:
          enabled: true
      routes:
......
        - id: gitegg-admin-monitor
          uri: lb://gitegg-admin-monitor
          predicates:
            - Path=/gitegg-admin-monitor/**
          filters:
            - StripPrefix=1
        - id: monitor
          uri: lb://gitegg-admin-monitor
          predicates:
            - Path=/monitor/**
          filters:
            - StripPrefix=0
......
8、啟動(dòng)所有的微服務(wù)羡洛,并訪問 http://127.0.0.1/gitegg-admin-monitor/monitor/login 進(jìn)行健康檢查微服務(wù)配置挂脑。

??根據(jù)我們在Nacos中的配置,我們這里的登錄用戶名密碼是:user / password


登錄頁

默認(rèn)應(yīng)用列表頁

應(yīng)用墻

??以上為SpringBootAdmin在SpringCloud微服務(wù)中的搭建和配置步驟欲侮,相比較而言比較簡單崭闲,但是一定要注意權(quán)限問題,不要因?yàn)榻】禉z查而泄露了系統(tǒng)信息威蕉。我們這里是通過Gateway進(jìn)行的統(tǒng)一鑒權(quán)刁俭,在生產(chǎn)環(huán)境部署時(shí),一定要注意修改默認(rèn)的Basic校驗(yàn)用戶名密碼忘伞,甚至需要修改健康檢查端點(diǎn)薄翅。

GitEgg-Cloud是一款基于SpringCloud整合搭建的企業(yè)級微服務(wù)應(yīng)用開發(fā)框架,開源項(xiàng)目地址:

Gitee: https://gitee.com/wmz1930/GitEgg
GitHub: https://github.com/wmz1930/GitEgg

歡迎感興趣的小伙伴Star支持一下氓奈。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市鼎天,隨后出現(xiàn)的幾起案子舀奶,更是在濱河造成了極大的恐慌,老刑警劉巖斋射,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件育勺,死亡現(xiàn)場離奇詭異但荤,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)涧至,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進(jìn)店門腹躁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人南蓬,你說我怎么就攤上這事纺非。” “怎么了赘方?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵烧颖,是天一觀的道長。 經(jīng)常有香客問我窄陡,道長炕淮,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任跳夭,我火速辦了婚禮涂圆,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘币叹。我一直安慰自己润歉,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布套硼。 她就那樣靜靜地躺著卡辰,像睡著了一般。 火紅的嫁衣襯著肌膚如雪邪意。 梳的紋絲不亂的頭發(fā)上九妈,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天,我揣著相機(jī)與錄音雾鬼,去河邊找鬼萌朱。 笑死,一個(gè)胖子當(dāng)著我的面吹牛策菜,可吹牛的內(nèi)容都是我干的晶疼。 我是一名探鬼主播,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼又憨,長吁一口氣:“原來是場噩夢啊……” “哼翠霍!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蠢莺,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤寒匙,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后躏将,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體锄弱,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡考蕾,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了会宪。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片肖卧。...
    茶點(diǎn)故事閱讀 39,841評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖掸鹅,靈堂內(nèi)的尸體忽然破棺而出塞帐,到底是詐尸還是另有隱情,我是刑警寧澤河劝,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布壁榕,位于F島的核電站,受9級特大地震影響赎瞎,放射性物質(zhì)發(fā)生泄漏牌里。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一务甥、第九天 我趴在偏房一處隱蔽的房頂上張望牡辽。 院中可真熱鬧,春花似錦敞临、人聲如沸态辛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽奏黑。三九已至,卻和暖如春编矾,著一層夾襖步出監(jiān)牢的瞬間熟史,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工窄俏, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蹂匹,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓凹蜈,卻偏偏與公主長得像限寞,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子仰坦,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評論 2 354

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