05 【UiBot 开发者指南】- 语言参考

语言参考

除了可视化视图之外,还有很多用户喜欢使用UiBot的源代码视图,来编写一个流程块。源代码视图是使用一种编程语言BotScript来描述流程块的,在这一章,我们先学习这种编程语言的基本规则,为后面学习源代码视图打下基础。当然,如果您根本不打算使用源代码视图,那么本章的内容实际上可以不看。

本章需要读者有一点点编程基础,任何编程语言都可以,只要了解变量、函数等基本概念即可。如果完全没有基础,建议先阅读[附录中的编程基础简介][附录A 编程基础知识],以便快速入门。

概述

[前文][RPA平台和UiBot]提到,UiBot的设计理念是“强大”、“简单”、“快捷”。简言之,UiBot既要让没有计算机基础的初学者,通过简单的学习,即可快速掌握流程的编写方法;又要让有一定编程基础的专业人员,能够以最快的速度实现自己的流程。

为了实现这些指标,UiBot提供了可视化的流程编写界面,便于初学者快速掌握;同时提供了一种简单、易学、接近自然语言的编程语言,便于专业人员的快速实现。当然,同一个流程块,可以用两种界面来显示,并可以在开发过程中随时切换。

本文档主要介绍UiBot提供的编程语言的基本语法规则。具有基本编程基础的读者,大约在两小时内即可掌握此规则,再经过数个小时的熟悉,即可灵活运用。对于有按键精灵基础的读者,还能进一步缩短学习时间。

对于UiBot来说,编程语言只是表达逻辑的工具,关键的功能还是由函数库或插件来实现。所以,语言设计只包括基本的逻辑,所有具体的功能,哪怕是最基本的“延时”功能,都不列入语言设计中,而在函数库中单独设计。本章内容亦不包括函数库的介绍。

UiBot的编程语言是专门设计的(下文简称UB语言),而不是市面上流行的编程语言如Python、JavaScript等,是因为UiBot的主要受众是那些非计算机专业科班出身,但足够熟悉业务流程的非技术人员。UB语言的设计尽可能的接近自然语言,对于理解基本英文单词的人来说,即使没有学习过,也能大致读懂。

相比之下,以JavaScript为例,虽然JavaScript是一种很棒的语言,在专业的程序员手里能发挥出很高的效率,甚至UiBot本身都有一部分代码是使用JavaScript编写的。但这种语言里面大量使用的括号,容易给非专业人员的学习带来障碍。如下图:

![复杂的JavaScript]!(https://upload-images.jianshu.io/upload_images/3353491-99c311062682ee70.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

因此,我们设计了专门的UB语言,并使这门语言尽可能简化,甚至尽可能少用除了字母和数字之外的元素。实际上,我们也考虑过使用市面上流行的编程语言的可能性,因为如果这样做,我们的开发工作量会大大降低,但与此同时,您的学习难度则会大大提高。所以,我们否定了这种思路,决定不采用流行的编程语言如Python等,非不能也,是不为也。

但是,在UB语言中,吸取了很多其他编程语言的优点。您会在UB语言的设计中看到Basic语言、Python语言、JavaScript语言的一些特点。因为我们在充分理解的基础上,博取众家之长,吸取最容易理解且常用的部分,删去复杂、不常用的部分,使UB语言精简、简单、易学、易用。

我们认为UB语言是目前最适合RPA领域的编程语言。

基本结构

UB语言的源代码文件是纯文本格式,扩展名不限,一律采用UTF-8编码。

UB语言的源代码由多条语句组成,和一般的脚本型语言,如Python、JavaScript等一样,UB语言并没有严格的结构和显式指定的入口。执行一个流程块的时候,从第一行开始执行,遇到函数定义暂时跳过,然后继续从函数结束后的一行开始执行(函数的概念请参考[这里][函数])。

一般来说,我们推荐一行只写一个语句。如果一定要写多个语句,则用冒号分隔符(:)进行分隔。

如果一行内容不够,需要折行,可以在任意语句中出现的逗号(,)或二元运算符(“二元运算符”的概念请参考[这里][运算符和表达式])之后直接折行,不需要增加其他额外的符号,也不推荐在其他地方折行。但如果一定要在其他地方折行,则用反斜杠(\)作为折行符号。例如:

Dim a= \
1

当一行中存在 // 时,表示从这以后的内容都是注释。包含在 /* */ 中的内容,无论多少行都视作注释。例如:

// 这里是注释
/*
这里
也是
注释
*/

注释在流程运行过程中没有任何作用,仅供我们阅读方便。

UB语言中所有关键字、变量名、函数名均不区分大小写。例如:变量名abc、ABC或者Abc都被认为是同一个变量。

变量、常量和数据类型

数据类型

变量是编程语言中最基础的功能,变量中可以存放数字、字符串等值,并且可以在运行的过程中,随时改变变量中的值。UB语言中的变量是动态类型的,即变量的值和类型都可以在运行过程中动态改变。这符合一般脚本型语言如Python、JavaScript的习惯。变量的类型分为以下几种:整数型、浮点数型、布尔型、字符串型、函数型、复合型和空值型。

整数型的值可以以十进制或者十六进制的方式表示,其中十六进制需加前缀 &H&h

浮点数的值可以用常规方式或者科学计数法方式表示。如 0.01 或者 1E-2 或者 1e-2 均代表同一个浮点数。

布尔型的值仅有 True 或者 False,两者皆不区分大小写。

字符串型的值用一对单引号(')或一对双引号(")所包围,字符串中可以用 \t 代表制表符,用 \n 代表换行,用 \' 代表单引号,用 \" 代表双引号,用 \\ 代表反斜杠本身。字符串中间可以直接换行,无需增加任何其他符号,换行符也会作为字符串的一部分。

也可以用前后各三个单引号(''')来表示一个字符串,这种字符串被称为长字符串。在长字符串中,可以直接写回车符、单引号和双引号,无需用 \n\' 或者 \"

函数型的值只能是已经定义好的函数,在后文详述。

复合型的值包括数组、字典等,在下一节详细阐述。

空值型的值总是 Null,不区分大小写。

例如:

a = 1           // a是整数型变量
a = &HFF        // a还是整数型变量
a = True        // a是布尔型变量。作为动态类型语言,a的类型可以随时变化 
a = FALSE       // a是布尔型变量,注意True和False都不区分大小写
a = 'UiBot'     // a是字符串型变量
a = "UiBot
RPA"            // a是字符串型变量,字符串中可以换行
a = null        // a是空值型变量,可以写为Null、NULL或null(不区分大小写)

变量和常量

变量的定义方式是:

Dim 变量名

定义变量名的同时,可以给变量赋值一个初始值:

Dim 变量名=值

想要定义多个变量的话,可以这样定义:

Dim 变量名1 = 值1, 变量名2
Dim 变量名1 = 值1, 变量名2 = 值2

常量的定义方式和变量类似,只是把Dim改为Const,并且必须在定义时就指定值:

Const 常量名=值, 常量名=值

常量和变量的唯一区别是,常量只能在定义时指定一次值,后面不允许再修改。

例如:

Dim a               // 定义名为a的变量,暂不赋值
Dim b = 1           // 定义名为b的变量,并赋值为1 
Dim c, d = True     // 定义名为c和d的两个变量,为d赋值True
Const e = 'UiBot'   // 定义名为e的常量,为其赋值为字符串’UiBot’
Const f             // 错误:常量必须有初始赋值

对于有命名的东西(例如:变量、常量、函数等),其名字统称为标识符,标识符需要遵循一定规则定义。

标识符可以用英文字母、下划线(_),任意UTF-8编码中包含的除英语以外其他语言的字符(当然,也包括汉字)表示,除了第一个字符外,后面还可以使用0-9的数字。变量名不区分大小写。

UB语言规定变量必须经过定义才能使用(除了For语句中的循环变量、Try语句中的异常变量、函数参数等)。变量在函数范围内定义时,属于局部变量,在函数退出时即清空。在函数范围之外任何位置定义时,属于全局变量,在运行过程中不会清空。全局变量可以定义在函数范围外任何位置,不影响其使用,甚至可以在使用变量之后定义。

复合类型

除了常用的整数型、字符串型等数据类型之外,UB语言还包括两种复合类型:数组、字典。两者在定义时和普通变量并无区别。

数组的值可以采用这种方式书写:

[值1, 值2, 值3 ]

其中值可以是任意类型,同一个数组中的不同值也可以是不同类型,值甚至可以是另外一个数组,这样就构成了一般意义上的多维数组。

字典的值可以采用这种方式初始化:

{ 名字1:值1, 名字2:值2, 名字3:值3 }

其中 名字 只能是字符串,值可以是任意类型。如果您熟悉JavaScript或者JSON,会发现这种初始化方法和JSON的表示形式高度相似。

无论是数组还是字典,要引用其中的元素,均采用方括号作为索引,如果要引用数组中的数组(即多维数组),或字典中的数组,可以继续在后面写新的方括号,如:

变量名[索引1][索引2]

用这种方式表示的数组中的元素或者是字典中的元素,既可以作为左值也可以作为右值,也就是说,既可以使用其中的值,也可以为其中的内容赋值,甚至可以在其中增加新的值。

例如:

Dim 变量 = [486, 557, 256]                      // 变量可以用中文命名,初值是一个数组
a = 变量[1]                                     // 此时a被赋值为557
变量 = {"key1":486, "key2":557, "key3":256}     // 变量的类型改为一个字典
a = 变量["key1"]                                // 此时a被赋值为486
变量["key4"] = [235, 668]                       // 往字典中增加一个新值,可以是一个数组
//此时,字典中的内容为 {"key1":486, "key2":557, "key3":256, "key4":[235, 668]}
a = 变量["key4"][0]                             // 此时a被赋值为235

注意:在引用数组或字典中的元素时,数组的索引只能是整数类型,用0作为起始索引;字典的索引只能是字符串类型。如果未能正确的使用,会在运行时报错。

运算符和表达式

UB语言中的运算符及其含义如下表:

+ - * / & ^ < <=
加法 减法/求负 乘法 除法 连接字符串 求幂 小于 小于等于
> >= <> = And Or Not Mod
大于 大于等于 不相等 相等/赋值 逻辑与 逻辑或 逻辑非 取余数

把变量、常量和值用运算符和圆括号 ( ) 连接到一起,称为表达式。在上述运算符中,Not 是一元运算符、- 既可以用作一元运算符,也可以用作二元运算符,其他都是二元运算符。一元运算符只允许在右边出现一个元素(变量、常量、表达式或值),二元运算符只允许在左右两边同时出现两个元素。

注意:当 = 出现在表达式内部时,其含义是判断是否相等。当 = 构成一个独立的语句时,其含义是赋值。这里 = 的设计虽然具有二义性,但能更好的被初学者所接受。

UB语言中删掉一些其他语言中具备、但不常用的运算符,如整数除运算符、位操作运算符等等。因为这些运算符的使用场景较少,即使需要,也可以采用其他方式实现。

表达式常用于赋值语句,可以给某个变量赋值,其形式为:

变量名 = 表达式

注意,当表达式为一个独立的(没有使用任何运算符计算)数组、字典类型的变量时,赋值操作只赋值其引用,也就是说,只是为这个变量增加一个“别名”。当一个数组、字典中的元素发生改变时,另一个也会改变。

例如:

a = [486, 557, 256]     // a是一个数组
b = a                   // b是a的“别名”
b[1] = 558              // 改变b里面的值,a里面的值也会跟着改变
c = a[1]                // 此时c的值是558,而不是原来的557
a = 557                 // 此时a被赋值为557(变为整数型)
b = a                   // 此时b里面的值也是557,但和a分别保存
b = 558                 // b里面的值发生改变,a的值不改变
c = a                   // 此时c的值仍然是原来的557,因为a不是字典、数组

逻辑语句

条件分支语句

即一般编程语言中最常用的If…Else语句,主要用于对某一个或者多个条件进行判断,从而执行不同流程。在UB语言中,有以下几种形式:

If 条件
    语句块1
End If
If 条件
    语句块1
Else
    语句块2
End If
If 条件1
    语句块1
ElseIf 条件2
    语句块2
Else
    语句块3
End If

当条件满足时,会执行条件之后的语句块,否则,语句块不会执行。Else后面的语句块则会在前面所有条件都不满足的时候,才会执行。

例如:

// Time.Hour() 可以取得当前时间中的小时数
// TracePrint() 可以把指定的内容输出到UiBot的输出栏中

If Time.Hour() > 18             // 取得当前时间中的小时数
    TracePrint("下班时间")      // 如果大于18,则执行这里的语句
Else
    TracePrint("上班时间")      // 如果不满足前面的条件,则执行这里的语句
End If

选择分支语句

根据一定的条件,选择多个分支中的一个。先计算Select Case后面的表达式,然后判断是否有某个Case分支和这个表达式的值是一致的。如果没有一致的Case分支,则执行Case Else(如果有)后面的语句块。

Select Case 表达式
    Case 表达式1, 表达式2
        语句块1
    Case 表达式3, 表达式4
        语句块2
    Case Else
        语句块3
End Select

例如:

Select Case Time.Month()            // 取得当前时间中的月份
    Case 1,3,5,7,8,10,12            // 如果是1、3、5、7、8、10、12月
        DayOfMonth = 31             // 当月有31天
    Case 4,6,9,11                   // 如果是4、6、9、11月
        DayOfMonth = 30             // 当月有30天
    Case Else                       // 如果是其他(也就是2月)
        DayOfMonth = 28             // 当月有28天(不考虑闰年的情况)
End Select
TracePrint(DayOfMonth)

条件循环语句

在UB语言中,使用Do…Loop语句来实现条件循环,即满足一定条件时,循环执行某一语句块。Do…Loop语句有以下五种不同的形式,用法较为灵活:

  1. 前置条件成立则循环:先判断条件条件成立则循环执行语句块,否则自动退出循环。
Do While 条件
    语句块
Loop
  1. 前置条件不成立则循环:和前一条相反,条件成立则退出循环,否则循环执行语句块。
Do Until 条件
    语句块
Loop
  1. 后置条件成立则循环:先执行语句块,再判断条件条件成立则继续循环执行语句块,否则自动退出循环。
Do
    语句块
Loop While 条件
  1. 后置条件不成立则循环:先执行语句块,再判断条件条件成立则自动退出循环,否则继续循环执行语句块。
Do 
    语句块
Loop Until 条件
  1. 无限循环:该循环语句本身不进行任何条件的判断,需要在语句块中自行做判断,如果语句块中没有跳出循环的语句,则会无限的执行该循环
Do
    语句块
Loop

例如:

Do Until Time.Hour() > 18              // 判断当前时间中的小时数,只要不大于18就循环
    TracePrint("还没有到下班时间")      // 每次循环,都会执行这里的语句
    Delay(1000)                        // 每判断一次,休息一秒钟
Loop
TracePrint("下班时间到啦")              // 如果大于18,则跳出循环,执行这里的语句

计次循环语句

计次循环语句主要用于执行一定次数的循环,其基本形式为:

For 循环变量 = 起始值 To 结束值 Step 步长
    语句块
Next

在计次循环语句中,起始值结束值步长都只允许是整数型或者浮点数型;步长可以省略,默认值为1。变量从起始值开始,每循环一次自动增加步长,直到大于结束值,循环才会结束。

在计次循环语句中,循环变量可以不用Dim语句定义,直接使用,但在循环结束后就不能再使用了。

例如:

Dim count = 0               // 定义变量count
For i=1 To 100              // 每次循环,变量i都会加1。这里变量i不需要定义
    count = count + i
Next
TracePrint(count)           // 这里会显示1+2+3+…+100的结果,即5050

遍历循环语句

遍历循环语句可以用于处理数组、字典中的每一个元素。遍历循环语句有以下两种形式:

For Each 循环变量 In 数组或字典
    语句块
Next

在这种形式的循环语句中,会自动遍历数组、字典中的每一个值,并将其置入循环变量中,直到遍历完成为止。

或者:

For Each 循环变量1, 循环变量2 In 数组或字典
    语句块
Next

在这种形式的循环语句中,会自动遍历数组、字典中的每一个索引和值,并将其分别置入循环变量1循环变量2中,直到遍历完成为止。

和计次循环语句类似,在遍历循环语句中,循环变量可以不用Dim语句定义,直接使用,但在循环结束后就不能再使用了。

例如:

Dim days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] // 定义数组型变量days
Dim count = 0   
For Each i In days             // 每次循环,变量i的值分别为days中的每个值 
    count = count + i          // 把数组中的每个值依次加起来
Next
TracePrint(count)              // 这里会显示一年中每个月的天数的累加和,即365

跳出语句

在UB语言中,支持以下形式的循环跳出语句:

Break

只能出现在条件循环、计次循环或遍历循环等循环语句的内部语句块中,其含义是立即跳出当前循环。

Continue

只能出现在条件循环、计次循环或遍历循环等循环语句的内部语句块中,其含义是立即结束当前循环,并开始下一次循环。

例如:

Dim days = { '一月':31, '二月':28, '三月':31, 
             '四月':30, '五月':31, '六月':30, 
             '七月':31, '八月':31, '九月':30, 
             '十月':31, '十一月':30, '十二月':31 }  // 定义字典型变量days

For Each i,j In days            // 每次循环,变量i, j分别为days中每个名字和值
    If j Mod 2 = 0              // 如果j是偶数
           Continue             // 结束本次循环,开始下一次循环
    End If
    TracePrint(i)               // 把days中的名字(其值不是偶数)显示出来
Next

另外,在流程块中的任何地方,只需要书写

Exit

不需要任何参数,即可在执行到此行的时候,自动结束整个流程(不是当前流程块)的执行。

函数

所谓函数,是指把一组常用的功能包装成一个语句块(称之为“定义”),并且可以在其他语句中运行这个语句块(称之为“调用”)。使用函数可以有效的梳理逻辑,以及避免重复代码的编写。

函数的定义和调用没有先后关系,可以先出现调用,再出现定义。但函数必须定义在全局空间中,也就是说,函数定义不能出现在其他函数定义、分支语句、循环语句下面的语句块中。

函数定义中可以包含参数,参数相当于是一个变量,但在调用时,可以由调用者指定这些变量的值。

定义函数的格式如下:

  • 无参数的函数
Function 函数名( )
    语句块
End Function
  • 有参数的函数
Function 函数名(参数定义1, 参数定义2)
    语句块
End Function

其中,参数定义的格式可以只是一个变量名,也可以是变量名 = 表达式的形式。对于后者来说,表示这个参数带有一个“默认值”,其默认值由“表达式”来确定。

如果函数有参数,则参数中的每个变量名都被认为是此函数内已经定义好的局部变量,无需使用Dim语句定义。

在函数定义中,要退出函数并返回,采用以下写法:

Return 返回值

当执行到这一语句时,将跳出函数并返回到调用语句的下一行。返回的时候可以带一个返回值(具体作用下文叙述)。返回值可以忽略,默认为Null。当执行到函数末尾的时候,无论有没有写Return语句,都会返回。

例如:

Function Add(x, y=1)        // 定义了两个参数的函数,第二个参数有默认值
    Return x + y            // 返回值为x+y的值
End Function

调用函数的格式如下:

返回 = 函数名(表达式1, 表达式2)

或者

函数名(表达式1,表达式2)

按照第一种格式调用,可以指定一个变量作为返回,当函数调用完成后,函数的返回值会自动赋值给这里的返回变量,调用者可以通过返回值,了解到函数调用的情况。此时,必须在被调用的函数名后面加圆括号。而当按照第二种格式调用时,调用者不需要返回值,则可以省略圆括号,使语句更符合自然语言习惯。

当调用时,相当于对函数中的参数进行了一次赋值运算,用表达式的值对其赋值。与赋值运算的规则相同,当表达式为一个独立的(没有使用任何运算符计算)数组、字典时,赋值操作只赋值其引用,也就是说,只是为变量增加一个“别名”。

调用函数时,传入的表达式的数量可以少于参数的数量,如果某个参数没有传入值,或者传入值为Null,则采用其默认值,如果连默认值也没有,则其值自动为Null

例如,对于上面定义的函数,可以按照如下的方式调用:

a = Add(100)            // 调用Add函数,第二个参数取默认值1,所以a的值是101
b = Add(100, 200)       // 调用Add函数,指定了两个参数,所以b的值是300
Add 100, 200            // 调用Add函数,不关心返回值,所以可以不写括号

当函数定义完成后,其名称可以作为一个函数类型的常量使用,也可以把函数名称赋值给某个变量,用这个变量也可以调用这个函数。

例如,对于上面定义的函数Add,可以按照如下的方式使用:

Dim Plus = Add
TracePrint Plus(100, 200)   
// 相当于先调用了Add函数,再用其返回值调用了TracePrint函数,结果是300

除了在流程块中定义的函数之外,UB语言中也已经内置了很多函数,可以完成各种丰富的功能。比如上面例子中的TracePrint就是一个内置函数。

其他

多模块

UB语言支持多模块,可以用其他语言实现扩展模块,并在当前流程块中使用。目前支持以下几种类型的模块:1)UB语言的流程块;2)Python语言的模块;3)C/C++语言的模块;4).Net的模块;5)Lua语言的模块。不同的模块有不同的扩展名,去掉扩展名以后,剩下的文件名就是模块的名字。比如某个Python语言的模块,文件名为Rest.py,则其模块名为Rest。

在UB语言中,采用以下方式导入一个模块:

Import 模块名

注意这里的模块名的书写规则和变量名一致,不需要采用双引号,也不需要加扩展名。如Import Rest。UiBot在编译和运行时会自动按照Lua语言模块、C语言模块、.Net语言模块、Python语言模块、UB语言流程块的先后顺序,依次加上相应的扩展名进行查找。在Windows中,由于文件名不区分大小写,所以Import语句后面的模块名也可以不区分大小写。在其他操作系统中,需要注意模块名的大小写要和文件一致。

每个导入的模块,都会被放置在一个与模块名同名的“命名空间”中,可以通过下面这种方式来调用导入模块中的函数:

命名空间.函数名

即在命名空间和函数名之间加一个点号(.)进行分隔。

对于Python、Lua语言的模块,只会保留其中的全局变量定义和函数定义,其他内容都会被忽略。对于C语言的模块和.Net模块,只能调用其中定义的函数。

如果要导入一个UB语言的流程块,则需要导入和被导入的流程块文件在同一个目录下。导入UB语言的流程块之后,既可以调用被导入的流程块中定义的函数,又可以直接以流程块的名字作为函数名,直接运行这个流程块中的所有命令。例如,有一个流程块 ABC.task。在其他流程块中Import之后,直接采用下面的格式即可直接调用ABC.task(相当于运行了ABC.task这个流程块):

ABC()

假设流程块 ABC.task中定义了一个函数,名为test,则可以采用下面的格式调用这个函数

ABC.test()

异常

作为动态类型语言,有很多错误在编译时难以检查,只能在运行时报错。而且,由于UiBot不强调运行速度,而更强调运行的稳定性,也会在运行时加入比较多的检查。当出错的时候,比较合适的报错手段是抛出异常。
比如,对于有目标命令(“有目标命令”的概念可以参考[这里][目标选取]),在运行的时候,如果到了超时时间都不能找到目标,就会自动抛出一个异常。

除了自动抛出的异常之外,在流程块中,还可以采用Throw语句抛出一个异常:

Throw 字符串

在抛出异常时,可以把异常相关信息以字符串的形式一起抛出,也可以省略这个字符串。

如果在流程块中没有对异常进行处理,当出现异常时,整个流程都会终止执行,并且把异常相关信息显示出来。如下图所示:

流程运行的时候出现异常

如果不希望流程在发生异常的时候终止,可以采用以下语句对异常进行处理:

Try
    语句块
Catch 变量名
    语句块
Else
    语句块
End Try

如果在Try后面的语句块中发生了异常,会跳到Catch后面的语句块中执行。如果在Try语句块中没有发生异常,且定义了Else语句块(当然,也可以省略Else语句块),则会跳到Else语句块中执行。

Catch语句后面的变量名可以省略。如果不省略,可以不用Dim语句提前定义,当发生异常时,这个变量的值是一个字典,其中包含“File”、“Line”和“Message”三个字段,分别代表发生异常的文件名、发生异常的行号、异常包含的信息。


返回目录

注: 上述内容经 UiBot 官方 授权发布,版权归 UiBot 官方所有,如需转载请先联系。

更多 RPA 相关的资讯,请关注公众号:流程自动化机器人教程
由于简书禁止直接在文章中插入公众号二维码,请点击 这里 了解添加该公众号的细节。

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

推荐阅读更多精彩内容