项目要点记录

一. 新建项目,创建UserController,增加test方法,能正常调用接口
二. 集成mybatis,打通数据库连接

  1. 添加mybatis 和 mysql依赖

    <dependency>
           <groupId>org.mybatis.spring.boot</groupId>
           <artifactId>mybatis-spring-boot-starter</artifactId>
           <version>2.2.2</version>
    <dependency>
    
    <dependency>
           <groupId>mysql</groupId>
           <artifactId>mysql-connector-java</artifactId>
    </dependency>
    
  2. 在application.properties中配置连接信息

    spring.datasource.name=imooc_mall_datasource
    spring.datasource.url=jdbc:mysql://127.0.0.1:3306/imooc_mall?useUnicode=true&characterEncoding=utf8&autoReconnect=true&useSSL=false&serverTimezone=Asia/Shanghai
    spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
    spring.datasource.username=root
    spring.datasource.password=root12345
    
  3. 生成跟表格一一对应的pojo model(pojo每个字段都跟数据库完全对应) (准备工作)

  4. 编写 UserMapper 接口 和 UserMapper.xml, 并关联起来

    ·在Application上添加mapper扫描, 指定mapper接口的路径

    @SpringBootApplication
    @MapperScan(basePackages = {"com.lc.mall.model.dao"})
    public class MallApplication {
        public static void main(String[] args) {
            SpringApplication.run(MallApplication.class, args);
        }
    }
    

    ·在application.properties中指定 mapper.xml的路径

    mybatis.mapper-locations=classpath:mappers/*.xml
    

    ·每个mapper和对应的mapper.xml 使用同样的文件名, 且在mapper.xml中指定namespace,跟mapper接口绑定

    @Repository
    public interface UserMapper {
       User findById(Integer id);
    } 
    
    <?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.lc.mall.model.dao.UserMapper">
       <select id="findById" parameterType="int" resultType="com.lc.mall.model.pojo.User">
          select * from imooc_mall_user where id = #{id}
       </select>
    </mapper> 
    

至此,实现mybatis的数据库打通。

三.封装接口统一返回对象(ApiResponse), 自定义Exception, 添加全局异常处理类,定义错误枚举

 public class ApiRestResponse {
    private Integer status;
    private String message;
    private Object data;

    private static final Integer OK_CODE = 10000;

    private static final String OK_MSG = "SUCCESS";

    public ApiRestResponse(Integer status, String message) {
        this.status = status;
        this.message = message;
    }

    public static ApiRestResponse success(Object object) {
        ApiRestResponse resp = new ApiRestResponse(OK_CODE, OK_MSG);
        resp.setData(object);
        return resp;
    }
 }
public class ImoocMallException extends RuntimeException {
  private Integer code;
  private String message;

  public ImoocMallException(Integer code, String message) {
      this.code = code;
      this.message = message;
  }

  public ImoocMallException(ImoocMallExceptionEnum exceptionEnum) {
      this.code = exceptionEnum.getCode();
      this.message = exceptionEnum.getMessage();
  }
} 
public enum ImoocMallExceptionEnum {

  NEED_USERNAME(10001, "用户名不能为空"),
  NEED_PASSWORD(10002, "密码不能为空"),
  PASSWORD_TOO_SHORT(10003, "密码长度不能小于8"),
  NAME_EXISTED(10004, "不允许重名"),
  INSERT_FAILED(10005, "插入失败, 请重试"),
  ;


  private Integer code;
  private String message;

  ImoocMallExceptionEnum(Integer code, String message) {
      this.code = code;
      this.message = message;
  }
} 

四.日志配置(集成log4j2)

  1. pom.xml中添加依赖
<!-- 移除logback依赖, 后面新增log4j2依赖, 不然类名有冲突-->
<exclusions>
  <exclusion>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-logging</artifactId>
  </exclusion>
</exclusions>

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-log4j2</artifactId>
</dependency> 
  1. log4j2配置文件 log4j2.xml
 <?xml version="1.0" encoding="UTF-8"?>
 <Configuration status="fatal">
    <Properties>
    <!--    指定日志保存路径-->
       <Property name="baseDir" value="/Users/luomeng/Desktop/Backend/java/images/"/>
    </Properties>

    <Appenders>
       <Console name="Console" target="SYSTEM_OUT">
          <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch) -->
          <ThresholdFilter level="info" onMatch="ACCEPT"
      onMismatch="DENY"/>
          <PatternLayout
      pattern="[%d{MM:dd HH:mm:ss.SSS}] [%level] [%logger{36}] - %msg%n"/>
       </Console>

  <!--debug级别日志文件输出-->
  <RollingFile name="debug_appender" fileName="${baseDir}/debug.log"
    filePattern="${baseDir}/debug_%i.log.%d{yyyy-MM-dd}">
    <!-- 过滤器 -->
    <Filters>
      <!-- 限制日志级别在debug及以上在info以下 -->
      <ThresholdFilter level="debug"/>
      <ThresholdFilter level="info" onMatch="DENY" onMismatch="NEUTRAL"/>
    </Filters>
    <!-- 日志格式 -->
    <PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
    <!-- 策略 -->
    <Policies>
      <!-- 每隔一天转存 -->
      <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
      <!-- 文件大小 -->
      <SizeBasedTriggeringPolicy size="100 MB"/>
    </Policies>
  </RollingFile>

  <!-- info级别日志文件输出 -->
  <RollingFile name="info_appender" fileName="${baseDir}/info.log"
    filePattern="${baseDir}/info_%i.log.%d{yyyy-MM-dd}">
    <!-- 过滤器 -->
    <Filters>
      <!-- 限制日志级别在info及以上在error以下 -->
      <ThresholdFilter level="info"/>
      <ThresholdFilter level="error" onMatch="DENY" onMismatch="NEUTRAL"/>
    </Filters>
    <!-- 日志格式 -->
    <PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
    <!-- 策略 -->
    <Policies>
      <!-- 每隔一天转存 -->
      <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
      <!-- 文件大小 -->
      <SizeBasedTriggeringPolicy size="100 MB"/>
    </Policies>
  </RollingFile>

  <!-- error级别日志文件输出 -->
  <RollingFile name="error_appender" fileName="${baseDir}/error.log"
    filePattern="${baseDir}/error_%i.log.%d{yyyy-MM-dd}">
    <!-- 过滤器 -->
    <Filters>
      <!-- 限制日志级别在error及以上 -->
      <ThresholdFilter level="error"/>
    </Filters>
    <!-- 日志格式 -->
    <PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
    <Policies>
      <!-- 每隔一天转存 -->
      <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
      <!-- 文件大小 -->
      <SizeBasedTriggeringPolicy size="100 MB"/>
    </Policies>
  </RollingFile>
</Appenders>
<Loggers>
  <Root level="debug">
    <AppenderRef ref="Console"/>
<!--      <AppenderRef ref="debug_appender"/>-->
    <AppenderRef ref="info_appender"/>
    <AppenderRef ref="error_appender"/>
  </Root>

</Loggers>
</Configuration>
  1. 在需要记录日志的地方获取logger并记录日志
private final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);

logger.error("Default Exception:", exception); 

五. 集成swagger实现接口文档

  1. 在pom.xml中添加依赖
<!--        swagger展示接口文档-->
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.9.2</version>
</dependency>

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>2.9.2</version>
</dependency> 
  1. 在Application上添加注解
@SpringBootApplication
@MapperScan(basePackages = {"com.lc.mall.model.dao"})
@EnableSwagger2
public class MallApplication {
    public static void main(String[] args) {
        SpringApplication.run(MallApplication.class, args);
    }
} 
  1. 添加swagger配置类
@Configuration
public class SpringFoxConfig {

    //访问http://localhost:8083/swagger-ui.html可以看到API文档
    @Bean
    public Docket api() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.any())
                .paths(PathSelectors.any())
                .build();
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("慕慕生鲜")
                .description("")
                .termsOfServiceUrl("")
                .build();
    }
} 
  1. 运行项目后访问 http://localhost:8080/swagger-ui.html
  2. spring-boot 2.6.3 集成swagger时, 运行报错 Failed to start bean 'documentationPluginsBootstrapper';
    是因为版本冲突,
    解决方案是:springboot2.6.x之后将spring MVC默认路径匹配策略从ANT_PATH_MATCHER模式改为PATH_PATTERN_PARSER模式导致出错,解决方法是切换会原先的ANT_PATH_MATCHER模式
spring.mvc.pathmatch.matching-strategy=ant_path_matcher 

六. 不常修改的数据, 可以对接口做缓存

  1. 使用spring-boot内置缓存
    ·· 先在Application上打开缓存
@SpringBootApplication
@MapperScan(basePackages = {"com.lc.mall.model.dao"})
@EnableSwagger2
@EnableCaching
public class MallApplication {
    public static void main(String[] args) {
        SpringApplication.run(MallApplication.class, args);
    }
} 

· 在需要缓存的方法上加上注解

// CategoryServiceImpl 中
@Cacheable(value = "listCategoryForCustomer")
public List<CategoryVO> listCategoryForCustomer(Integer parendId) {
    List<CategoryVO> result = new ArrayList<>();
    findList(parendId, result);
    return result;
} 
  1. 集成redis缓存
    · pom.xml中添加redis依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <version>2.6.3</version>
</dependency> 

· 在application.properties中配置redis连接信息

spring.redis.host=localhost
spring.redis.port=6379
spring.redis.password= 

· 增加缓存配置类

@Configuration
@EnableCaching
public class CachingConfig {

    @Bean
    public RedisCacheManager redisCacheManager(RedisConnectionFactory connectionFactory) {

        RedisCacheWriter redisCacheWriter = RedisCacheWriter.lockingRedisCacheWriter(connectionFactory);
        RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
        cacheConfiguration = cacheConfiguration.entryTtl(Duration.ofSeconds(30));

        RedisCacheManager redisCacheManager = new RedisCacheManager(redisCacheWriter, cacheConfiguration);
        return redisCacheManager;
    }
} 

· 在Application中打开缓存 和 在需要缓存的地方添加注解, 配置跟使用内置缓存相同

七. 使用aop记录所有请求的入参和返回值
· 在pom.xml中添加aop依赖

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

· 添加aop配置类

@Aspect
@Component
public class WebLogAspect {

    private final Logger log = LoggerFactory.getLogger(WebLogAspect.class);
    @Pointcut("execution(public * com.lc.mall.controller.*.*(..))")
    public void webLog(){

    }

    @Before("webLog()")
    public void doBefore(JoinPoint joinpoint){
        //收到请求,记录请求的信息
        ServletRequestAttributes attributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();

        log.info("URL:"+request.getRequestURL());
        log.info("HTTP_METHOD:"+request.getMethod());
        log.info("IP:"+request.getRemoteAddr());
        log.info("CLASS_METHOD:"+joinpoint.getSignature().getDeclaringTypeName()+"."+joinpoint.getSignature().getName());
        log.info("ARGS:"+ Arrays.toString(joinpoint.getArgs()));
    }

    @AfterReturning(returning = "res", pointcut = "webLog()")
    public void doAfter(Object res) throws JsonProcessingException {
        //处理完请求返回内容
        log.info("RESPONSE:"+ new ObjectMapper().writeValueAsString(res));
    }
} 

八. 查询功能增加分页功能
· 增加pagehelper依赖 (注:版本需要跟mybatis版本兼容, 不然启动时会报错com.github.pagehelper.autoconfigure.PageHelperAutoConfiguration)

<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
    <version>1.4.1</version>
</dependency> 

· 在需要实现分页的地方调用

public PageInfo listForAdmin(Integer pageNum, Integer pageSize) {
    PageHelper.startPage(pageNum, pageSize, "type, order_num");
    List<Category> categoryList = categoryMapper.selectList();
    PageInfo pageInfo = new PageInfo(categoryList);
    return pageInfo;
} 

九. 请求传入接口参数校验
有的springboot版本自动集成了 spring-boot-starter-validation,但是有的版本需要自己添加依赖(springboot2.6.3就需要手动添加)

  1. 添加依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency> 
  1. 使用注解进行校验
@ApiOperation("更新")
@PostMapping("/update")
public ApiRestResponse update(@RequestParam("signature") @NotNull(message = "签名信息不能为空") String signature, HttpSession session) {
} 
public class AddCategoryReq {

    @Size(min = 3, max = 8, message = "名称长度在3-8个字符")
    @NotNull(message = "名称不能为空")
    private String name;
    
    private Integer orderNum;

    private Integer parentId;

    @NotNull(message = "orderNum不能为null")
    @Max(value = 3)
    private Integer type;
} 
  1. 当参数校验不通过时, 会抛出异常 MethodArgumentNotValidException, 需要在全局异常处理中进行处理
@RestControllerAdvice
public class GlobalExceptionHandler {

    private final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    @ExceptionHandler(value = Exception.class)
    public ApiRestResponse handleException(Exception exception) {
        logger.error("Default Exception:", exception);
        return new ApiRestResponse(20000, "系统异常");
    }

    @ExceptionHandler(value = ImoocMallException.class)
    public ApiRestResponse handleMallException(ImoocMallException exception) {
        logger.error("ImoocMallException:", exception);
        return new ApiRestResponse(exception.getCode(), exception.getMessage());
    }

    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public ApiRestResponse handleValidException(MethodArgumentNotValidException e) {
        logger.error("MethodArgumentNotValidException:", e);
        return handleBindingResult(e.getBindingResult());
    }

    // 从异常提示中提取信息返回给前端
    private ApiRestResponse handleBindingResult(BindingResult result){
        //把异常处理为对外暴露的提示
        List<String> list = new ArrayList<>();
        if (result.hasErrors()) {
            List<ObjectError> allErrors = result.getAllErrors();
            for (int i = 0; i < allErrors.size(); i++) {
                ObjectError objectError = allErrors.get(i);
                String message = objectError.getDefaultMessage();
                list.add(message);
            }
        }
        if (list.size() == 0){
            return ApiRestResponse.error(ImoocMallExceptionEnum.REQUEST_PARAM_ERROR);
        }
        return ApiRestResponse.error(ImoocMallExceptionEnum.REQUEST_PARAM_ERROR.getCode(), list.toString());
    }
} 

十. 过滤器使用
1.编写过滤器 继承 Filter

/**
 * 用户过滤器
 * @author Rex
 * @create 2021-02-09 13:52
 */
public class UserFilter implements Filter {
    
    public static User currentUser;

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpSession session = request.getSession();
        currentUser = (User) session.getAttribute(Constant.IMOOC_MALL_USER);
        if (currentUser == null) {
            PrintWriter out = new HttpServletResponseWrapper((HttpServletResponse) servletResponse).getWriter();
            out.write("{\n" +
                    "    \"status\": 10007,\n" +
                    "    \"msg\": \"NEED_LOGIN\",\n" +
                    "    \"data\": null\n" +
                    "}");
            out.flush();
            out.close();
            return;
        }
        filterChain.doFilter(servletRequest, servletResponse);
    }
} 
  1. 添加过滤器配置
/**
 * User过滤器的配置
 * @author Rex
 * @create 2021-02-09 13:59
 */
@Configuration
public class UserFilterConfig {

    @Bean
    public UserFilter userFilter(){
        return new UserFilter();
    }

    @Bean(name = "userFilterConf")
    public FilterRegistrationBean userFilterConfig(){
        FilterRegistrationBean<Filter> filterFilterRegistrationBean = new FilterRegistrationBean<>();
        filterFilterRegistrationBean.setFilter(userFilter());
        filterFilterRegistrationBean.addUrlPatterns("/cart/*");
        filterFilterRegistrationBean.addUrlPatterns("/order/*");
        filterFilterRegistrationBean.setName("userFilterConfig");
        return filterFilterRegistrationBean;
    }
} 

一般每个过滤器只做一件事,需要多个过滤器时, 使用相同的步骤进行配置即可

十一. 图片上传接口

  1. 定义接口
file.upload.dir=/Users/luomeng/Desktop/Backend/java/images/
file.upload.ip=127.0.0.1 
@Component
public class Constant {
    public static String FILE_UPLOAD_DIR;

    @Value("${file.upload.dir}")
    public void setFileUploadDir(String fileUploadDir) {
        FILE_UPLOAD_DIR = fileUploadDir;
    }
} 
/**
* 上传文件, 保存文件到指定路径并返回图片url
* */
@PostMapping("/upload/file")
public ApiRestResponse upload(HttpServletRequest httpRequest, @RequestParam("file") MultipartFile file) {

    String fileName = file.getOriginalFilename();
    String fileType = fileName.substring(fileName.lastIndexOf("."));
    String newFileName = UUID.randomUUID().toString() + fileType;

    File fileDir = new File(Constant.FILE_UPLOAD_DIR);
    File destFile = new File(Constant.FILE_UPLOAD_DIR + newFileName);
    if (!fileDir.exists()) {
        if (!fileDir.mkdir()) {
            throw new ImoocMallException(ImoocMallExceptionEnum.MKDIR_FAILED);
        }
    }
    try {
        // 文件写入
        file.transferTo(destFile);
    } catch (IOException e) {
        throw new ImoocMallException(ImoocMallExceptionEnum.FILE_UPLOAD_FAILED);
    }

    // 拼接url并返回
    try {
        String url = getHost(new URI(httpRequest.getRequestURL()+"")) + "/images/" + newFileName;
        return ApiRestResponse.success(url);
    } catch (URISyntaxException e) {
        e.printStackTrace();
        return ApiRestResponse.error(ImoocMallExceptionEnum.FILE_UPLOAD_FAILED);
    }
}

private URI getHost(URI uri){
    URI effectiveURI;
    try {
        effectiveURI = new URI(uri.getScheme(), uri.getUserInfo(), uri.getHost(), uri.getPort(), null,null, null);
    } catch (URISyntaxException e) {
        e.printStackTrace();
        effectiveURI = null;
    }
    return effectiveURI;
} 
  1. 增加配置,将返回的图片地址指定到本机图片存储的目录(配置后才能通过返回的图片链接访问图片)
@Configuration
public class ImoocMallWebMvcConfig implements WebMvcConfigurer {
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/admin/**")
                .addResourceLocations("classpath:/static/admin/");
        registry.addResourceHandler("/images/**")
                .addResourceLocations("file:"+ Constant.FILE_UPLOAD_DIR);
        registry.addResourceHandler("swagger-ui.html").addResourceLocations(
                "classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**").addResourceLocations(
                "classpath:/META-INF/resources/webjars/");


    }
} 

十二. 当遇到查询结果与预期不符, 想查看执行的sql时,如下配置可打印出mybatis最终运行的sql语句

在application.properties中配置mybatis

mybatis.mapper-locations=classpath:mappers/*.xml
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl 

十三. 项目中配置多环境(如:开发环境dev, 生产环境prod等)
在application.properties同级目录中分别创建各个环境的配置文件,文件名格式:application
-xx.properties, (如:application-dev.xml, application-prod.xml)

在application.properties文件中指定使用环境

spring.profiles.active=prod 

一些坑:

  1. 当数据库的字段和pojo模型中的属性不一致时,需要在mapper.xml中指定映射关系
<resultMap id="userMap" type="com.lc.mall.model.pojo.User">
   <result column="personalized_signature" property="personalizedSignature"></result>
   <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
   <result column="update_time" jdbcType="TIMESTAMP" property="updateTime" />
</resultMap>

<select id="findById" parameterType="int" resultType="com.lc.mall.model.pojo.User" resultMap="userMap">
   select * from imooc_mall_user where id = #{id}
</select> 
  1. 当像数据库插入数据时,需要同步获取自动生成的id主键时,使用 useGeneratedKeys="true" 和 keyProperty="id"同时使用
 <insert id="insert" parameterType="com.lc.mall.model.pojo.User" useGeneratedKeys="true" keyProperty="id">
      insert into imooc_mall_user (xxx, xxx,xxx) values (xxx,xxx,xxx)
 </insert> 
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,843评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,538评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,187评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,264评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,289评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,231评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,116评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,945评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,367评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,581评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,754评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,458评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,068评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,692评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,842评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,797评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,654评论 2 354

推荐阅读更多精彩内容