C-Velocity(模板引擎) --- A版(代码生成器) --- 2021年5月30日 23:25:32

非框架类总纲

https://www.jianshu.com/p/d0167f082cbf

友情链接:Velocity-后传

https://www.jianshu.com/p/056525422f2e

前言

说到Java 开发中模板引擎,常见有三:Thymeleaf 、Velocity、Freemarker
当然市面上有很多文章关于三者的区别,个人认为
Thymeleaf :Springboot 官方支持,全栈 html 非常方便,若是前后端分离 vue 就完全不适用
Freemarker:很多老项目用看过大量使用,不支持多级编译(这是硬伤,同时也是有效的保护了代码),现在很多开源框架主流用它做代码生成器模板(elAdmin就是一款)
Velocity:类似的jsp语法,能多级编译,但文件名必须为vm,现在很多开源框架主流用它做代码生成器模板(人人fast就 是一款)

文章参考三者区别:

https://blog.csdn.net/mrleeapple/article/details/89883807
https://blog.csdn.net/qq_41720208/article/details/80891393
https://www.cnblogs.com/ywb-articles/p/10627398.html

1、velocity 简介

1、velocity 介绍

Velocity是一个基于Java的模板引擎,可以通过特定的语法获取在java对象的数据 , 填充到模板中,从而实现界面和java代码的分离 !


image.png
2、为什么学习velocity(应用场景)
  • Web应用程序 : 作为为应用程序的视图, 展示数据。
  • 源代码生成 : Velocity可用于基于模板生成Java源代码
  • 自动电子邮件 : 网站注册 , 认证等的电子邮件模板
  • 网页静态化 : 基于velocity模板 , 生成静态网页
3、velocity 组成原理
image.png

Velocity主要分为app、context、runtime和一些辅助util几个部分。

  • app模块 : 主要封装了一些接口 , 暴露给使用者使用。主要有两个类,分别是Velocity(单例)和VelocityEngine。
  • Context模块 : 主要封装了模板渲染需要的变量
  • Runtime模块 : 整个Velocity的核心模块,Runtime模块会将加载的模板解析成语法树,Velocity调用mergeTemplate方法时会渲染整棵树,并输出最终的渲染结果。
  • RuntimeInstance类为整个Velocity渲染提供了一个单例模式,拿到了这个实例就可以完成渲染过程了。

2、velocity引擎快速入门

1、创建工程、引入坐标、插件配置
image.png
 <dependencies>
        <dependency>
            <groupId>org.apache.velocity</groupId>
            <artifactId>velocity-engine-core</artifactId>
            <version>2.2</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <!--插件配置-->
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>utf-8</encoding>
                </configuration>
            </plugin>
        </plugins>
    </build>
2、编写模板

在项目resources目录下创建模板文件

image.png

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

hello , ${name} !

</body>
</html>
3、加载模板、合并输出结果
    @Test
    public void test1() throws IOException {
        //设置velocity资源加载器
        Properties prop = new Properties ( );
        prop.put ("file.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
        Velocity.init (prop);

        //创建Velocity容器
        VelocityContext context = new VelocityContext ( );
        context.put ("name", "阿K");

        //加载模板
        Template tpl = Velocity.getTemplate ("vms/01-quickstark.vm", "UTF-8");
        FileWriter fw = new FileWriter ("D:\\work\\demo1.html");

        //合并数据到模板
        tpl.merge (context, fw);

        //释放资源
        fw.close ( );
    }
4、运行原理分析
  • Velocity解决了如何在后台程序和网页之间传递数据的问题,后台代码和视图之间相互独立,一方的修改不影响另一方 .
  • 他们之间是通过环境变量(Context)来实现的,网页制作一方和后台程序一方相互约定好对所传递变量的命名约定,比如上个程序例子中的site, name变量,它们在网页上就是$name ,$site 。
  • 只要双方约定好了变量名字,那么双方就可以独立工作了。无论页面如何变化,只要变量名不变,那么后台程序就无需改动,前台网页也可以任意由网页制作人员修改。这就是Velocity的工作原理。


    image.png

3、vtl 模板语法

1、vtl介绍

Velocity Template Language (VTL) , 是Velocity 中提供的一种模版语言 , 旨在提供最简单和最干净的方法来将动态内容合并到网页中。简单来说VTL可以将程序中的动态数展示到网页中
VTL的语句分为4大类:注释 , 非解析内容 ** , 引用指令**。

2、注释

2-1. 行注释

## 行注释内容

2-2. 块注释

#*
块注释内容1
块注释内容2
*#

2-3. 文档注释

#**
文档注释内容1
文档注释内容2
*#

2-4. 案例

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
## 我是行注释

#*
* 我是块注释
* 呵呵呵
* *#

#**
* 我是文档注释
*
* *#
hello , ${name} !

</body>
</html>
3、非解析内容

所谓非解析内容也就是不会被引擎解析的内容。

3-1. 语法

#[[
非解析内容1
非解析内容2 
]]#

3-2. 示例

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

hello , ${name} !
    
<h1>非解析内容</h1>
#[[
直接输出的内容1
直接输出的内容2
${name}
]]#
</body>
</html>
4、变量语法

引用语句就是对引擎上下文对象中的属性进行操作。语法方面分为常规语法(`$属性`)和正规语法(${属性})。
4-1. 语法

$变量名, 若上下文中没有对应的变量,则输出字符串"$变量名"
${变量名},若上下文中没有对应的变量,则输出字符串"${变量名}" 
$!变量名, 若上下文中没有对应的变量,则输出空字符串"" 
$!{变量名}, 若上下文中没有对应的变量,则输出空字符串""

4-2. 示例

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>引用变量</h1>
常规语法 : $name
正规语法 : ${name}

## 如果获取的变量不存在, 表达式会原样展示 , 如果不想展示 , 可以使用 $!变量名
## 以下写法的含义代表么如果有变量, 那么获取变量值展示, 没有变量展示""
常规语法 : $!name
正规语法 : $!{name}

</body>
</html>
5、属性语法

5-1. 语法

$变量名.属性,    若上下文中没有对应的变量,则输出字符串"$变量名.属性"
${变量名.属性}   若上下文中没有对应的变量,则输出字符串"${变量名.属性}"
$!变量名.属性    若上下文中没有对应的变量,则输出字符串""
$!{变量名.属性}  若上下文中没有对应的变量,则输出字符串""

5-2. 示例

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>引用属性</h1>
常规语法 : $user.username --- $user.password
正规语法 : ${user.username} --- ${user.password}

正规语法 : ${user.email} --- ${user.email}
正规语法 : $!{user.email} --- $!{user.email}

</body>
</html>
6、方法语法

方法引用实际就是指方法调用操作,关注点返回值参数 , 方法的返回值将输出到最终结果中
6-1. 语法

$变量名.方法([入参1[, 入参2]*]?), 常规写法
${变量名.方法([入参1[, 入参2]*]?)}, 正规写法

$!变量名.方法([入参1[, 入参2]*]?), 常规写法
$!{变量名.方法([入参1[, 入参2]*]?)}, 正规写法

6-2. 示例

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>引用属性</h1>
$str.split(" ")
${str.split(" ")}
$time.getTime()
${time.getTime()}

</body>
</html>
7、常用指令
流程控制
    #set
    #if/#elseif/#else
    #foreach
    #break 
    #stop
引入外部的模板
    #parse
    #include
    #evaluate
    #define
宏指令
7.1 #set

7-1-0. 作用:在页面中声明定义变量

7-1-1. 语法: #set($变量 = 值)

7-1-2. 示例

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>set指令</h1>
#set($str = "hello world")
#set($int = 1)
#set($arr = [1,2])
#set($boolean = true)
#set($map = {"key1":"value1", "key2":"value2"})

## 在字符串中也可以引用之前定义过的变量
#set($str2 = "$str , how are you !")
#set($str3 = '$str , how are you !')

<h1>获取set指令定义的变量</h1>
${str}
${int}
${arr}
${boolean}
${map.key1}--${map.key2}
${str2}
${str3}

</body>
</html>
7.2 #if/#elseif/#else

7-2-0. 作用:进行逻辑判断
7-2-1. 语法:

#if(判断条件)
  .........
#elseif(判断条件)
  .........
#else
  .........
#end 

7-2-2. 示例

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>if/elseif/else指令</h1>
#set($language="PHP")

#if($language.equals("JAVA"))
    java开发工程师
#elseif($language.equals("PHP"))
    php开发工程师
#else
    开发工程师
#end

</body>
</html>
7.3 #foreach

7-3-0. 作用:遍历循环数组或者集合
7-3-1. 语法:

#foreach($item in $items)
    ..........
    [#break]
#end
  • $items : 需要遍历的对象或者集合
    • 如果items的类型为map集合, 那么遍历的是map的value
  • $item : 变量名称, 代表遍历的每一项
  • #break : 退出循环
  • 内置属性 :
    • $foreach.index : 获取遍历的索引 , 从0开始
    • $foreach.count : 获取遍历的次数 , 从1开始

7-3-2. 示例

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<h1>遍历数组</h1>
#foreach($str in $hobbies)
    ${foreach.index} -- ${str}  <br>
#end

<h1>变量对象集合</h1>
<table border="1px" align="center">
    <tr>
        <td>编号</td>
        <td>用户名</td>
        <td>密码</td>
        <td>邮箱</td>
        <td>年龄</td>
        <td>操作</td>
    </tr>
    #foreach($user in $users)
        <tr>
            <td>${foreach.index}</td>
            <td>${user.username}</td>
            <td>${user.password}</td>
            <td>${user.email}</td>
            <td>${user.age}</td>
            <td>
                <a href="">编辑</a>
                <a href="">删除</a>
            </td>
        </tr>
    #end
</table>

<h1>遍历map集合</h1>
<h2>遍历值</h2>
 #foreach($value in $map)
     $value
 #end

<h2>遍历键值对</h2>
#foreach($entry in $map.entrySet())
    $entry.key -- $entry.value
#end


</body>
</html>
7.4 #include

7-4-0. 作用:引入外部资源 , 引入的资源不会被引擎所解析
7-4-1. 语法:#include(resource)

  • resource可以为单引号或双引号的字符串,也可以为$变量,内容为外部资源路径。
  • 注意 : 路径如果为相对路径,则以引擎配置的文件加载器加载路径作为参考(也就是 resource 的路径)


    image.png

7-4-2. 示例

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

#include("vms/01-quickstark.vm")

</body>
</html>
7.5 #parse

7-5-0. 作用:引入外部资源 , 引入的资源将被引擎所解析
7-5-1. 语法:#parse(resource)

  • resource可以为单引号或双引号的字符串,也可以为$变量,内容为外部资源路径。
  • 注意 : 路径如果为相对路径,则以引擎配置的文件加载器加载路径作为参考系

7-5-2. 示例

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

#parse("vms/01-quickstark.vm")

</body>
</html>
7.6 #define

7-6-0. 作用:定义重用模块(不带参数)
7-6-1. 语法:

#define($模块名称)
    模块内容
#end

7-6-2. 示例

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>定义模块</h1>
#define($table)
<table border="1px" align="center">
    <tr>
        <td>编号</td>
        <td>用户名</td>
        <td>密码</td>
        <td>年龄</td>
        <td>操作</td>
    </tr>
    <tr>
        <td>0</td>
        <td>zhangsan</td>
        <td>123456</td>
        <td>18</td>
        <td>
            <a href="">编辑</a>
            <a href="">删除</a>
        </td>
    </tr>
    <tr>
        <td>1</td>
        <td>lisi</td>
        <td>123456</td>
        <td>18</td>
        <td>
            <a href="">编辑</a>
            <a href="">删除</a>
        </td>
    </tr>
    <tr>
        <td>2</td>
        <td>wangwu</td>
        <td>123456</td>
        <td>18</td>
        <td>
            <a href="">编辑</a>
            <a href="">删除</a>
        </td>
    </tr>

</table>
#end

<h1>使用模块</h1>
$table
$table
$table

</body>
</html>
7.7 #evaluate

7-7-0. 作用:动态计算 , 动态计算可以让我们在字符串中使用变量
7-7-1. 语法:#evalute("计算语句")
7-7-2. 示例
后端:这样直接传过去的 $code,会被直接当做字符串解析,需要使用 #evaluate才不会被直接输出

        //创建Velocity容器
        VelocityContext context = new VelocityContext ( );
        context.put ("code", "#if($language.equals(\"JAVA\"))\n" +
                "    java开发工程师\n" +
                "#elseif($language.equals(\"PHP\"))\n" +
                "    php开发工程师\n" +
                "#else\n" +
                "    开发工程师\n" +
                "#end");

vms:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>动态计算</h1>
#set($language= "java")

#evaluate($code)

</body>
</html>
7.8 宏指令

7-8-0. 作用:定义重用模块(可带参数)
7-8-1. 语法
7.8-1-1、定义语法:

#macro(宏名 [$arg]?)
   .....
#end

7.8-1-1、调用语法:

#宏名([$arg]?)

7-8-2. 示例

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>定义宏</h1>
#macro(table $list)
<table border="1px">
    <tr>
        <td>编号</td>
        <td>用户名</td>
        <td>密码</td>
        <td>邮箱</td>
        <td>年龄</td>
        <td>操作</td>
    </tr>
    #foreach($item in $list)
    <tr>
        <td>${foreach.count}</td>
        <td>${item.username}</td>
        <td>${item.password}</td>
        <td>${item.email}</td>
        <td>${item.age}</td>
        <td>
            <a href="">编辑</a>
            <a href="">删除</a>
        </td>
    </tr>
    #end
</table>
#end

<h1>调用宏</h1>
#table($users)

</body>
</html>

4、.综合案例

1、代码生成介绍

在实际项目开发过程中, 编写基础的CRUD操作代码, 往往会花费我们大量的时间 , 而且这些CRUD代码的基础结构基本上是固定的 , 如果能有一个代码生成器 , 能够帮助我们把这些代码生成出来 , 我们就可以节省出大量的时间关注核心业务代码的开发, 大大提高了我们的开发效率 !

需求 : 使用velocity实现一个简单的代码生成器 , 生成项目开发过程中的基础CRUD代码

2、代码生成流程分析
  1. 创建项目
  2. 导入依赖
  3. 编写模板
  4. 生成代码
3、编写模板

一般我们的项目开发将项目分为三层 , 我们的代码生成器就基于传统的三层架构生成代码 , 所以我们需要为每一层的每一个类创建模板 , 所以需要有如下模板 :

  • Controller.java.vm : 控制层模板
  • Service.java.vm : 业务层接口模板
  • ServiceImpl.java.vm : 业务层实现模板
  • Dao.java.vm : 数据服务层模板(数据访问层基于通用Mpper实现)

Controller.java.vm:

package ${package}.controller;

import ${package}.pojo.${className};
import ${package}.service.${className}Service;
import ${package}.utils.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/${classname}")
public class ${className}Controller {

    @Autowired
    private ${className}Service ${classname}Service ;


    /**
     * 查询列表
     * @return
     */
    @RequestMapping("/list")
    public Result list(){
        List<${className}>  ${classname}s = null;
        try {
                ${classname}s = ${classname}Service.list();
            return Result.ok(${classname}s);
        } catch (Exception e) {
            e.printStackTrace();
            return Result.error("查询数据异常");
        }
    }

    /**
     * 保存
     * @param ${classname}
     * @return
     */
    @RequestMapping("/save")
    public Result save(@RequestBody ${className} ${classname}){
        try {
                ${classname}Service.save(${classname});
            return Result.ok("新增数据成功");
        } catch (Exception e) {
            e.printStackTrace();
            return Result.error("新增数据异常");
        }
    }

    /**
     * 更新
     * @param ${classname}
     * @return
     */
    @RequestMapping("/update")
    public Result update(@RequestBody ${className} ${classname}){
        try {
                ${classname}Service.update(${classname});
            return Result.ok("修改数据成功");
        } catch (Exception e) {
            e.printStackTrace();
            return Result.error("修改数据异常");
        }
    }

    /**
     * 删除
     * @param ids
     * @return
     */
    @RequestMapping("/delete")
    public Result delete(@RequestBody Integer[] ids){
        try {
                ${classname}Service.delete(ids);
            return Result.ok("删除数据成功");
        } catch (Exception e) {
            e.printStackTrace();
            return Result.error("删除数据异常");
        }
    }
}

Service.java.vm

package ${package}.service;

import com.kk.pojo.${className};

import java.util.List;
import java.util.Map;

public interface ${className}Service {

    /**
     * 查询数据列表
     * @return
     */
    List<${className}> list();

    /**
     * 保存数据
     * @param ${classname}
     */
    void save(${className} ${classname});

    /**
     * 更新数据
     * @param ${classname}
     */
    void update(${className} ${classname});

    /**
     * 删除数据
     * @param ids
     */
    void delete(Integer[] ids);
}

ServiceImpl.java.vm

package ${package}.service.impl;

import ${package}.dao.${className}Dao;
import ${package}.pojo.${className};
import ${package}.service.${className}Service;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

public class ${className}ServiceImpl  implements ${className}Service {

    @Autowired
    private ${className}Dao ${classname}Dao ;

    public List<${className}> list() {
        return ${classname}Dao.selectAll();
    }

    public void save(${className} ${classname}) {
            ${classname}Dao.insert(${classname});
    }

    public void update(${className} ${classname}) {
            ${classname}Dao.updateByPrimaryKey(${classname});
    }

    public void delete(Integer[] ids) {
        Stream.of(ids).forEach(${classname}Dao::deleteByPrimaryKey);
    }
}

Dao.java.vm

package ${package}.dao;

import com.kk.pojo.${className};
import tk.mybatis.mapper.common.Mapper;

public interface ${className}Dao extends Mapper<${className}> {
}
4、实现代码生成逻辑
package com.kk.utils;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;

import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * 代码生成器   工具类
 */
public class GenUtils {

    private static String currentTableName;

    public static List<String> getTemplates() {
        List<String> templates = new ArrayList<String>();
        templates.add("vms/Controller.java.vm");
        templates.add("vms/Service.java.vm");
        templates.add("vms/ServiceImpl.java.vm");
        templates.add("vms/Dao.java.vm");

        return templates;
    }


    /**
     * 生成代码
     */
    public static void generatorCode(Map<String, Object> data, List<String> templates, ZipOutputStream zip) {

        //设置velocity资源加载器
        Properties prop = new Properties();
        prop.put("file.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
        Velocity.init(prop);

        //封装模板数据
        VelocityContext context = new VelocityContext(data);

        //获取模板列表
        for (String template : templates) {
            //渲染模板
            StringWriter sw = new StringWriter();
            Template tpl = Velocity.getTemplate(template, "UTF-8");
            tpl.merge(context, sw);

            try {
                //添加到zip
                zip.putNextEntry(new ZipEntry(getFileName(template, data.get("className").toString(), data.get("package").toString())));
                IOUtils.write(sw.toString(), zip, "UTF-8");
                IOUtils.closeQuietly(sw);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }



    /**
     * 获取文件名 , 每个文件所在包都不一样, 在磁盘上的文件名几路径也各不相同
     */
    public static String getFileName(String template, String className,String packageName) {
        String packagePath = "main" + File.separator + "java" + File.separator;
        if (StringUtils.isNotBlank(packageName)) {
            packagePath += packageName.replace(".", File.separator) + File.separator;
        }

        if (template.contains("Dao.java.vm")) {
            return packagePath + "dao" + File.separator + className + "Dao.java";
        }

        if (template.contains("Service.java.vm")) {
            return packagePath + "service" + File.separator + className + "Service.java";
        }

        if (template.contains("ServiceImpl.java.vm")) {
            return packagePath + "service" + File.separator + "impl" + File.separator + className + "ServiceImpl.java";
        }

        if (template.contains("Controller.java.vm")) {
            return packagePath + "controller" + File.separator + className + "Controller.java";
        }

        return null;
    }
}

运行测试

public class GeneratorCodeTest {

    public static void main(String[] args) throws IOException {
        Map<String,Object> data = new HashMap<String,Object>();
        data.put("className","Product");
        data.put("classname","product");
        data.put("package","com.itheima");

        File file = new File("D:\\Users\\Desktop\\code.zip");
        FileOutputStream outputStream = new FileOutputStream(file);
        ZipOutputStream zip = new ZipOutputStream(outputStream);

        GenUtils.generatorCode(data,GenUtils.getTemplates(),zip);

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

推荐阅读更多精彩内容