[MyBatis源码详解 - 解析器模块 - 组件一] XNode

一、功能

  常用的解析XML的方式有三种,详见:Java解析xml的三种方式,其中最常用的是DOM,在DOM中每个XML的节点都是一个Node(org.w3c.dom.Node),Node通常跟XPath配合使用,提供了解析节点元素名属性名属性值节点文本内容嵌套节点等功能,要想熟练地使用这些功能,需要对XPath表达式解析的语法非常熟悉。
  XNode封装了Node,提供了常见的解析一个Node节点需要的功能和方法。

二、属性

  XNode的属性组成如图:

XNode_field.jpg

  node:被包装的org.w3c.dom.Node对象
  name:节点名
  body:节点内容
  attributes:节点属性集合
  variables:mybatis-config.xml配置文件中<properties>节点下引入或定义的键值对
  xpathParser:封装了XPath解析器,XNode对象由XPathParser对象生成,并提供了解析XPath表达式的功能

eg:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration   PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration> 
    <properties>
        <property name="database.driver" value="com.mysql.jdbc.Driver"/>
        <property name="database.url" value="jdbc:mysql://localhost:3306/ssm?useSSL=false"/>
        <property name="database.username" value="root"/>
        <property name="database.password" value="root"/>
    </properties>

    <typeAliases> <!-- 别名 -->
        <typeAlias alias="role" type="com.learn.ssm.chapter4.pojo.Role"/>
    </typeAliases>
</configuration>

  以<typeAlias>节点为例子,生成一个对应的XNode节点后,name值为"typeAlias",由于没有文本内容也没有子节点所以body为空,attributes值为{alias=role, type=com.learn.ssm.chapter4.pojo.Role}的一个Properties对象,variables{database.driver=com.mysql.jdbc.Driver, database.url=jdbc:mysql://localhost:3306/ssm?useSSL=false, database.username=root, database.password=root},xpathParser是构造函数传进来的一个参数,只需要知道它提供了解析XPath表达式的功能即可。

三、构造函数
    public XNode(XPathParser xpathParser, Node node, Properties variables) {
        this.xpathParser = xpathParser;
        this.node = node;
        this.name = node.getNodeName();  // name是调用Node的方法获取
        this.variables = variables;      // variables是外部传入的构造参数
        this.body = parseBody(node);
        this.attributes = parseAttributes(node);
    }
四、方法

  解析节点内容调用的是parseBody方法,先从该方法入手分析。

1、private String parseBody(Node node)
    // 提供解析节点文本内容的功能
    private String parseBody(Node node) {
        String data = getBodyData(node);  
        if (data == null) {
            NodeList children = node.getChildNodes();
            for (int i = 0; i < children.getLength(); i++) {
                Node child = children.item(i);
                data = getBodyData(child);
                if (data != null) {
                    break;
                }               
            }           
        }
        return data;
    }

【功能】提供解析节点文本内容的功能
【源码分析】getBodyData方法可以获取节点文本内容,只有文本类型的节点才能返回字符串值,否则返回空,如果一个节点形如<A>aaa</A>,则返回"aaa",后面parseBody直接返回即可;如果非文本节点,则进入下面的if分支,分支中先去获取该节点下的所有子节点,并且逐个获取子节点的文本内容(假如子节点时文本节点的话),一旦获取到第一个文本子节点的文本内容,并且内容非空,则跳出循环返回,如下所示,A会在解析第二个子节点C时拿到其文本内容cbody跳出循环返回。

<A>
  <B att="val"/>
  <C>cbody</C>
  <D>dbody</D>
</A>


2、private String getBodyData(Node child)
    private String getBodyData(Node child) {
        /**
         *  只处理文本类型的节点: Node.CDATA_SECTION_NODE、Node.TEXT_NODE
         *                 Node.COMMENT_NODE(不需要判断,因为XPathParser加载XML时已经设置了忽略注释)
         */
        if (child.getNodeType() == Node.CDATA_SECTION_NODE ||
            child.getNodeType() == Node.TEXT_NODE) {
            String data = ((CharacterData) child).getData();
            data = PropertyParser.parse(data, variables);
            return data;
        }
        return null;
    }

【功能】获取文本节点文本内容
【源码分析】代码很简单,PropertyParser.parse(data, variables);只是为了解析带占位符的变量的值,比如解析到的文本内容为${database.driver},则data会被进一步解析成com.mysql.jdbc.Driver,此处暂时不深究PropertyParser是怎么实现的。



  解析节点属性调用的是parseAttributes方法,接下来分析parseAttributes方法。

3、private Properties parseAttributes(Node node)
    // 解析节点属性键值对,并将其放入Properties对象中,对外提供根据属性名差属性值功能时用到
    private Properties parseAttributes(Node node) {
        Properties attributes = new Properties();
        NamedNodeMap attributeNodes = node.getAttributes();
        if (attributeNodes != null) {
            for (int i = 0; i < attributeNodes.getLength(); i++) {
                Node attribute = attributeNodes.item(i);
                String value = PropertyParser.parse(attribute.getNodeValue(), variables);
                attributes.put(attribute.getNodeName(), value);
            }
        }
        return attributes;
    }

【功能】获取所有节点属性
【源码分析】先通过Node.getAttributes()获取到了包含所有节点属性的NamedNodeMap对象,接着遍历该对象,拿到属性名和属性值,放入要返回的Properties对象中。


4、public XNode getParent()
    // 获取当前节点的父节点
    public XNode getParent() {
        Node parent = node.getParentNode();
        if (parent == null || !(parent instanceof Element)) {
            return null;
        } else {
            return new XNode(xpathParser, parent, variables);
        }
    }

【功能】获取当前节点的父节点并包装为XNode
【源码分析】如果是顶层节点或非元素节点,则返回空,否则创建。


5、public String getPath()
    // 获取节点路径
    public String getPath() {
        StringBuilder builder = new StringBuilder();
        Node current = node;
        while (current != null && current instanceof Element) {
            if (current != node) {
                builder.insert(0, "/");
            }
            builder.insert(0, current.getNodeName());
            current = current.getParentNode();
        }
        return builder.toString();
    }

【功能】获取节点路径
【源码分析】获取从当前节点到顶层节点的路径,在while循环中每次current都会获取其父节点,一层层向上追溯,直到顶层节点,比如<A><B><C></C></B></A>,对C节点来说节点路径就是A/B/C


6、public String getValueBasedIdentifier()
    // 获取节点值的识别码,优先级: id > value > property
    public String getValueBasedIdentifier() {
        StringBuilder builder = new StringBuilder();
        XNode current = this;
        while (current != null) {
            if (current != this) {
                builder.insert(0, "_");
            }
            String value = current.getStringAttribute("id", 
                            current.getStringAttribute("value", 
                             current.getStringAttribute("property", null)));
            if (value != null) {
                value = value.replace('.', '_');
                builder.insert(0, "]");
                builder.insert(0, value);
                builder.insert(0, "[");
            }
            builder.insert(0, current.getName());
            current = current.getParent();
        }
        return builder.toString();
    }

【功能】获取节点值的识别码,优先级: id > value > property
【源码分析】获取一个能唯一标识节点的字符串,如下面的C节点,返回的唯一标识字符串为A_B[bid]_C[cid],类似于获取节点路径,也会一层层追溯到顶层节点。

<A>
  <B id="bid">
    <C id="cid" value="cvalue"/>
  </B>
</A>



6、eval*()系列方法
    public String evalString(String expression) {
        return xpathParser.evalString(node, expression);
    }
    
    public Boolean evalBoolean(String expression) {
        return xpathParser.evalBoolean(node, expression);
    }
    
    public Double evalDouble(String expression) {
        return xpathParser.evalDouble(node, expression);
    }
    
    public XNode evalNode(String expression) {
        return xpathParser.evalNode(node, expression);
    }
    
    public List<XNode> evalNodes(String expression) {
        return xpathParser.evalNodes(node, expression);
    }

【功能】调用XPathParser方法在当前节点下寻找符合表达式条件的节点,通常是文本节点,并将其值转化为指定的类型,如果值无法转化为指定类型会报错。
【支持数据类型】 String、Boolean、Double、Node、List<Node>
【源码分析】简单调用XPathParser提供的方法


7、get*Body()系列方法(以getBooleanBody为例)
    public Boolean getBooleanBody() {
        return getBooleanBody(null);
    }
    
    public Boolean getBooleanBody(Boolean def) {
        if (body == null) {
            return def;
        } else {
            return Boolean.valueOf(body);
        }
    }

【功能】获取文本节点内容并将其转化为指定的数据类型
【支持的数据类型】 String、Boolean、Integer、Long、Double、Float
【源码分析】简单判断body是否为空,不为空则做类型转换


7、get*Attribute()系列方法(以getStringAttribute为例)
    // 获取属性值,如果没有返回null
    public String getStringAttribute(String name) {
        return getStringAttribute(name, null);
    }
    
    // 获取属性是,没有没有使用默认值
    public String getStringAttribute(String name, String def) {
        String value = attributes.getProperty(name);
        if (value == null) {
            return def;
        } else {
            return value;
        }
    }

【功能】获取节点指定属性的属性值并将其转化为指定的数据类型
【支持的数据类型】 Enum、String、Boolean、Integer、Long、Double、Float
【源码分析】从attributes中根据属性名取出属性值再坐简单类型转化


8、public List<XNode> getChildren()
    // 获取子节点,对Node.getChildNodes()做相应的封装得到List<XNode>
    public List<XNode> getChildren() {
        List<XNode> children = new ArrayList<XNode>();
        NodeList nodeList = node.getChildNodes();
        if (nodeList != null) {
            // 我的写法
            /*for (int i = 0; i < nodeList.getLength(); i++) {
                Node node = nodeList.item(i);
                if (node instanceof Element) {
                    children.add(new XNode(xpathParser, node, variables));
                }
            }*/
            
            // 源码的写法
            for (int i = 0, n = nodeList.getLength(); i < n; i++) {
                Node node = nodeList.item(i);
                if (node.getNodeType() == Node.ELEMENT_NODE) {
                    children.add(new XNode(xpathParser, node, variables));
                }
            }
        }
        return children;
    }

【功能】获取子节点,对Node.getChildNodes()做相应的封装得到List<XNode>
【源码分析】取出包装的Node对象的所有子元素节点,并对其包装成XNode列表返回


9、public Properties getChildrenAsProperties()
    // 获取所有子节点的name、value属性键值对
    public Properties getChildrenAsProperties() {
        Properties properties = new Properties();
        for (XNode child : getChildren()) {
            String name = child.getStringAttribute("name");
            String value = child.getStringAttribute("value");
            if (name != null && value != null) {
                properties.put(name, value);
            }
        }
        return properties;
    }

【功能】获取所有子节点的name、value属性键值对
【源码分析】先调用getChildren()获得节点的所有子XNode,然后逐一遍历获取其name、value,放在Properties对象中返回。


五、测试案例
1、测试XML文件
<?xml version="1.0" encoding="UTF-8"?>
<configuration attr1="haha" attr2="hehe">
    <evalType name="name1" value="value1">
        <evalString1>evalString1_text</evalString1>
        <evalString2>${evalString2}</evalString2>
        <evalBoolean>false</evalBoolean>
        <evalDouble>3.14156</evalDouble>
        <evalNode id="id">Node</evalNode>
        <evalNode2 id="id" value="value" property="property">Node</evalNode2>
        <evalNode3 value="value" property="property">Node</evalNode3>
        <evalNode4 property="property">Node</evalNode4>
        <evalInt>100</evalInt>
        <evalLong>20000000000</evalLong>
        <evalFloat>6.667</evalFloat>
    </evalType>
    
    <getTypeAttribute name="name2" value="value2">
        <getStringAttribute value="string"/>
        <getBooleanAttribute value="false"/>
        <getIntAttribute value="100"/>
        <getLongAttribute value="2000000000"/>
        <getDoubleAttribute value="3.1415"/>
        <getFloatAttribute value="6.667"/>
        <style font="Aharoni"/>
    </getTypeAttribute>
    
</configuration>


2、测试案例
package org.apache.ibatis.parsing.test;

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.List;
import java.util.Properties;

import org.apache.ibatis.parsing.XNode;
import org.apache.ibatis.parsing.XPathParser;

enum Font { Aharoni, Aldhabi, Algerian }

public class XNodeTest {
    public static void main(String[] args) throws Exception {
        // 1. 加载XML文件
        InputStream inputStream = new BufferedInputStream(new FileInputStream("src/main/java/org/apache/ibatis/parsing/test/XNodeTest.xml"));
        
        // 2. 生成XPathParser对象
        Properties variables = new Properties();
        variables.put("evalString2", "evalString2_text");
        XPathParser parser = new XPathParser(inputStream, false, variables, null);
        
        // 3. 生成XNode对象
        XNode configuration = parser.evalNode("/configuration");
        System.out.println("getName(): " + configuration.getName());
        System.out.println("toSting(): " + configuration);
        
        // 4. 测试XNode的eval*()系列方法
        System.out.println("【testEvalType】");
        testEvalType(configuration);
        
        //  5. 节点路径、父节点
        System.out.println("【getPath()、getParent()】");
        XNode evalNode =  configuration.evalNode("evalType/evalNode");
        System.out.println("getPath(): " + evalNode.getPath());
        XNode evalNodeParent = evalNode.getParent();
        System.out.println("getParent(): " + evalNodeParent);
        
        // 6. 节点识别码
        System.out.println("【getValueBasedIdentifier(): id > value > property】");
        XNode evalNode2 =  configuration.evalNode("evalType/evalNode2");
        XNode evalNode3 =  configuration.evalNode("evalType/evalNode3");
        XNode evalNode4 =  configuration.evalNode("evalType/evalNode4");
        System.out.println("getValueBasedIdentifier(): [property] " + evalNode4.getValueBasedIdentifier());
        System.out.println("getValueBasedIdentifier(): [value > property] " + evalNode3.getValueBasedIdentifier());
        System.out.println("getValueBasedIdentifier(): [id > value > property] " + evalNode2.getValueBasedIdentifier());
        System.out.println();
        
        // 7. 测试get*Body()系列方法
        System.out.println("【testEvalBody】");
        testGetTypeBody(configuration.evalNode("evalType"));
        System.out.println();
        
        // 8. 测试get*Attribute()系列方法
        System.out.println("【testGetTypeAttribute】");
        testGetTypeAttribute(configuration.evalNode("getTypeAttribute"));
        System.out.println();
        
        // 9. getChildren()
        System.out.println("【getChildren()】");
        List<XNode> children = configuration.getChildren();
        System.out.println("children num is " + children.size());
        for (XNode node : children) {
            System.out.println(node.getPath());         
        }
        System.out.println();
        
        // 10. getChildrenAsProperties()
        System.out.println("【getChildrenAsProperties】");
        Properties properties = configuration.getChildrenAsProperties();
        System.out.println(properties);
        System.out.println();
        
        // 11. parseAttributes()
        System.out.println("【parseAttributes()】");
        System.out.println(configuration.getStringAttribute("attr1"));
        System.out.println(configuration.getStringAttribute("attr2"));
    }
    
    public static void testEvalType(XNode root) {
        String evalString1 = root.evalString("evalType/evalString1/text()");
        String evalString2 = root.evalString("evalType/evalString2/text()");
        Boolean evalBoolean = root.evalBoolean("evalType/evalBoolean/text()");
        Double evalDouble = root.evalDouble("evalType/evalDouble/text()");
        XNode evalNode =  root.evalNode("evalType/evalNode");
        List<XNode> evalNodes = root.evalNodes("evalType/*");
        
        System.out.println("evalString(expr): " + evalString1);
        System.out.println("evalString(expr) with var: " + evalString2);
        System.out.println("evalBoolean(expr): " + evalBoolean);
        System.out.println("evalDouble(expr): " + evalDouble);
        System.out.println("evalNode(expr): " + evalNode);
        System.out.println("evalNodes(expr): nodeList size is " + evalNodes.size());
        for (int i = 0; i < evalNodes.size(); i++) {
            System.out.println("nodeList[" + i + "] = " + evalNodes.get(i));
        }
    }
    
    public static void testGetTypeBody(XNode root) {        
        XNode node = root.evalNode("evalString1/text()");
        XNode node1 = root.evalNode("evalBoolean/text()");
        XNode node2 = root.evalNode("evalInt/text()");
        XNode node3 = root.evalNode("evalLong/text()");
        XNode node4 = root.evalNode("evalDouble/text()");
        XNode node5 = root.evalNode("evalFloat/text()");
        String getStringBody = node.getStringBody();
        Boolean getBooleanBody = node1.getBooleanBody();
        Integer getIntBody = node2.getIntBody();
        Long getLongBody = node3.getLongBody();
        Double getDoubleBody = node4.getDoubleBody();
        Float getFloatBody = node5.getFloatBody();
        
        System.out.println("getStringBody(): " + getStringBody);
        System.out.println("getBooleanBody(): " + getBooleanBody);
        System.out.println("getIntBody(): " + getIntBody);
        System.out.println("getLongBody(): " + getLongBody);
        System.out.println("getDoubleBody(): " + getDoubleBody);
        System.out.println("getFloatBody(): " + getFloatBody);
    }
    
    public static void testGetTypeAttribute(XNode root) {
        XNode node = root.evalNode("getStringAttribute");
        XNode node1 = root.evalNode("getBooleanAttribute");
        XNode node2 = root.evalNode("getIntAttribute");
        XNode node3 = root.evalNode("getLongAttribute");
        XNode node4 = root.evalNode("getDoubleAttribute");
        XNode node5 = root.evalNode("getFloatAttribute");
        XNode node6 = root.evalNode("style");
        
        System.out.println("getStringAttribute(name): " + node.getStringAttribute("value"));
        System.out.println("getBooleanAttribute(name): " + node1.getBooleanAttribute("value"));
        System.out.println("getIntAttribute(name): " + node2.getIntAttribute("value"));
        System.out.println("getLongAttribute(name): " + node3.getLongAttribute("value"));
        System.out.println("getDoubleAttribute(name): " + node4.getDoubleAttribute("value"));
        System.out.println("getFloatAttribute(name): " + node5.getFloatAttribute("value"));
        System.out.println("getEnumAttribute(Class, name): " + node6.getEnumAttribute(Font.class, "font"));
    }
}


3、输出结果
#consoles

getName(): configuration
toSting(): <configuration attr2="hehe" attr1="haha">
<evalType name="name1" value="value1">
<evalString1>evalString1_text</evalString1>
<evalString2>evalString2_text</evalString2>
<evalBoolean>false</evalBoolean>
<evalDouble>3.14156</evalDouble>
<evalNode id="id">Node</evalNode>
<evalNode2 value="value" property="property" id="id">Node</evalNode2>
<evalNode3 value="value" property="property">Node</evalNode3>
<evalNode4 property="property">Node</evalNode4>
<evalInt>100</evalInt>
<evalLong>20000000000</evalLong>
<evalFloat>6.667</evalFloat>
</evalType>
<getTypeAttribute name="name2" value="value2">
<getStringAttribute value="string"/>
<getBooleanAttribute value="false"/>
<getIntAttribute value="100"/>
<getLongAttribute value="2000000000"/>
<getDoubleAttribute value="3.1415"/>
<getFloatAttribute value="6.667"/>
<style font="Aharoni"/>
</getTypeAttribute>
</configuration>

【testEvalType】
evalString(expr): evalString1_text
evalString(expr) with var: evalString2_text
evalBoolean(expr): true
evalDouble(expr): 3.14156
evalNode(expr): <evalNode id="id">Node</evalNode>

evalNodes(expr): nodeList size is 11
nodeList[0] = <evalString1>evalString1_text</evalString1>

nodeList[1] = <evalString2>evalString2_text</evalString2>

nodeList[2] = <evalBoolean>false</evalBoolean>

nodeList[3] = <evalDouble>3.14156</evalDouble>

nodeList[4] = <evalNode id="id">Node</evalNode>

nodeList[5] = <evalNode2 value="value" property="property" id="id">Node</evalNode2>

nodeList[6] = <evalNode3 value="value" property="property">Node</evalNode3>

nodeList[7] = <evalNode4 property="property">Node</evalNode4>

nodeList[8] = <evalInt>100</evalInt>

nodeList[9] = <evalLong>20000000000</evalLong>

nodeList[10] = <evalFloat>6.667</evalFloat>

【getPath()、getParent()】
getPath(): configuration/evalType/evalNode
getParent(): <evalType name="name1" value="value1">
<evalString1>evalString1_text</evalString1>
<evalString2>evalString2_text</evalString2>
<evalBoolean>false</evalBoolean>
<evalDouble>3.14156</evalDouble>
<evalNode id="id">Node</evalNode>
<evalNode2 value="value" property="property" id="id">Node</evalNode2>
<evalNode3 value="value" property="property">Node</evalNode3>
<evalNode4 property="property">Node</evalNode4>
<evalInt>100</evalInt>
<evalLong>20000000000</evalLong>
<evalFloat>6.667</evalFloat>
</evalType>

【getValueBasedIdentifier(): id > value > property】
getValueBasedIdentifier(): [property] configuration_evalType[value1]_evalNode4[property]
getValueBasedIdentifier(): [value > property] configuration_evalType[value1]_evalNode3[value]
getValueBasedIdentifier(): [id > value > property] configuration_evalType[value1]_evalNode2[id]

【testEvalBody】
getStringBody(): evalString1_text
getBooleanBody(): false
getIntBody(): 100
getLongBody(): 20000000000
getDoubleBody(): 3.14156
getFloatBody(): 6.667

【testGetTypeAttribute】
getStringAttribute(name): string
getBooleanAttribute(name): false
getIntAttribute(name): 100
getLongAttribute(name): 2000000000
getDoubleAttribute(name): 3.1415
getFloatAttribute(name): 6.667
getEnumAttribute(Class, name): Aharoni

【getChildren()】
children num is 2
configuration/evalType
configuration/getTypeAttribute

【getChildrenAsProperties】
{name2=value2, name1=value1}

【parseAttributes()】
haha
hehe



六、我的github(注释源码、测试案例)

   [仓库地址] huyihao/mybatis-source-analysis
   [注释源码] XNode.java
   [测试案例源码] XNodeTest.java

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