ABP+AdminLTE+Bootstrap Table权限管理系统第七节--登录逻辑及abp封装的Javascript函数库

ABP+AdminLTE+Bootstrap Table权限管理系统一期
Github:https://github.com/Jimmey-Jiang/ABP-ASP.NET-Boilerplate-Project-CMS
前往博客园总目录:ABP+AdminLTE+Bootstrap Table权限管理系统一期

经过前几节,我们已经解决数据库,模型,DTO,控制器和注入等问题.那么再来看一下登录逻辑.这里算是前面几节的一个初次试水.
首先我们数据库已经有的相应的数据.



模型和DTO已经建好,所以我们直接在服务层添加Login方法就可以了.



在展现层添加Account控制器,注入IUserService接口,调用Login方法.

然后添加视图页面.


运行一下,看一下结果.




除了页面比较漂亮(哈哈),这些本来都没有什么好说的,直接上图,
这里值得注意的是,我们在创建下面的方法,在调用接口的的时候会报一个错误:web的App_Data/Logs/Logs.txt日志文件中查看到打印到错误信息。
public async Task<ListResultDto<UserInfoDto>> GetUsers()
        {
            var users = await _userRepository.GetAllListAsync();

            return new ListResultDto<UserInfoDto>(
                users.MapTo<List<UserInfoDto>>()
                );
        }

错误提示: Mapper not initialized. Call Initialize with appropriate configuration. If you are trying to use mapper instances through a container or otherwise, make sure you do not have any calls to the static Mapper.Map methods, and if you're using ProjectTo or UseAsDataSource extension methods, make sure you pass in the appropriate IConfigurationProvider instance.
意思是:映射器未初始化。 通过适当的配置调用初始化。 如果您尝试通过容器或其他方式使用映射器实例,请确保您没有任何调用静态Mapper.Map方法,如果您使用ProjectTo或UseAsDataSource扩展方法,请确保您传入相应的IConfigurationProvider实例。
原因:Mapper not initialized 映射未初始化
解决:在ApplicationModule类中增加依赖typeof(AbpAutoMapperModule)即可。

using System.Reflection;
using Abp.Modules;
using Abp.AutoMapper;

namespace JCmsErp
{
    [DependsOn(typeof(JCmsErpCoreModule), typeof(AbpAutoMapperModule))]
    public class JCmsErpApplicationModule : AbpModule
    {
        public override void Initialize()
        {
            IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());
        }
    }
}

另外上面这个提示窗是不是很漂亮,登录页面的设计样式都是自己写的,自己这个弹窗,只是abp自己封装.我们来看一下具体的代码.

abp.message.confirm(
                    '请输入密码.', //确认提示
                    '确定?',//确认提示(可选参数)
                    function (isConfirmed) {
                        if (isConfirmed) {
                            //...delete user 点击确认后执行
                        }
                    }
                );

其实abp封装很多js函数库,这让我不得不感叹土牛(土耳其牛人)真是牛啊!看下图左边部分js文件



包括像abp.jquery.js,ABP服务端支持标准的ajax的请求/输出。建议大家使用abp.jquery.js中提供的ajax请求方法,这个方法基于jquery的ajax方法,可以自动处理服务端的异常信息,当然,如果你对js很熟练的话,也可以根据自己的需要写ajax。你也可以使用jquery的ajax方法调用,但是需要设置一下默认请求参数,dataType 设置为 'json', type 设置为 'POST' and contentType 设置为 'application/json,在发送请求时需要将js对象转换成json字符串,和$.ajax一样,你也可以传递参数覆盖abp.ajax的默认参数abp.ajax返回一个promise类型.Login上标记了 HttpPost 特性 abp.ajax默认以 POST 方式请求. 返回值被简化成了一个匿名对象。
消息
用于向用户显示对话框,展示消息或者得到用户的确认,ABP默认采用的sweetalert库实现的对话框信息,使用时你需要引用sweetalert的样式和js,在我的登录页面里面已经引用了,并且引用abp.sweet-alert.js就可以使用下列API了:

abp.message.info('some info message', 'some optional title');
abp.message.success('some success message', 'some optional title');
abp.message.warn('some warning message', 'some optional title');
abp.message.error('some error message', 'some optional title');

abp.message.confirm(
    'User admin will be deleted.', //确认提示
    'Are you sure?',//确认提示(可选参数)
    function (isConfirmed) {
        if (isConfirmed) {
            //...delete user 点击确认后执行
        }
    }
);

用户界面的繁忙提示
设置一个半透明层,阻止点击页面元素,可以覆盖局部或者整个页面,例子如下:

abp.ui.block(); //覆盖整个页面
abp.ui.block($('#MyDivElement')); //覆盖指定元素,可以把jquery对象作为参数
abp.ui.block('#MyDivElement'); //或者直接使用选择器参数
abp.ui.unblock(); //整个页面解除覆盖
abp.ui.unblock('#MyDivElement'); //指定元素解除覆盖

UI Block API使用blockUI这个js库来实现效果的,如果使用这个api需要在页面引用blockUI的js库和abp.blockUI.js文件。
UI Busy API 指示页面繁忙的API,如ajax请求中:

abp.ui.block = function (elm) {
        if (!elm) {
            $.blockUI();
        } else {
            $(elm).block();
        }
    };

    abp.ui.unblock = function (elm) {
        if (!elm) {
            $.unblockUI();
        } else {
            $(elm).unblock();
        }
    };

abp.ui.setBusy('#MyLoginForm');
abp.ui.clearBusy('#MyLoginForm');

Js日志接口
这个主要是对浏览器console.log('...') 进行的包装,可以支持所有浏览器,你可以通过设置abp.log.level来控制日志输出,和服务端一样,如设置了abp.log.levels为INFO时就不会输出debug日志了,你也可以根据你的需要定制重新这些API。

abp.notify.success = function (message, title, options) {
        abp.log.warn('abp.notify.success is not implemented!');
    };

    abp.notify.info = function (message, title, options) {
        abp.log.warn('abp.notify.info is not implemented!');
    };

    abp.notify.warn = function (message, title, options) {
        abp.log.warn('abp.notify.warn is not implemented!');
    };

    abp.notify.error = function (message, title, options) {
        abp.log.warn('abp.notify.error is not implemented!');
    };

格式化字符串(abp.utils.formatString)
和C#的string.Format一样的用法

/* Formats a string just like string.format in C#.
    *  Example:
    *  abp.utils.formatString('Hello {0}','Tuana') = 'Hello Tuana'
    ************************************************************/
    abp.utils.formatString = function () {
        if (arguments.length < 1) {
            return null;
        }

        var str = arguments[0];

        for (var i = 1; i < arguments.length; i++) {
            var placeHolder = '{' + (i - 1) + '}';
            str = abp.utils.replaceAll(str, placeHolder, arguments[i]);
        }

        return str;
    };

最后,当然我们登录页面也可以改一下.这样就更简洁了.

var url = "/Account/Login";
            //构建要传输的参数对象
            var newPerson = {
                userName:$("#userName").val(),
                password:$("#password").val() }
            };
            //调用abp的ajax方法
            abp.ajax({
                url:url,
                data: JSON.stringify(newPerson) //转换成json字符串
            }).done(function (data) {
                abp.message.warn('用户名或密码错误!', '登录失败');
            });

至此,我们登录逻辑,和JavaScript封装模块就全部完成了,其实abp提示窗还蛮好看的,大家也可以借鉴一下在自己的项目里面.
下面说下JavaScript的封装。

AJAX操作问题

现代的应用经常会使用AJAX,尤其是单页应用,几乎是和服务器通信的唯一手段,执行AJAX通常会有以下步骤:

  • 基本上:为了执行一个AJAX调用,首先你要在客户端提供一个可供请求的URL,选取提交数据和一个方法(GET,POST,PUT,DELETE)。

  • 等待调用完成后,处理返回结果。当执行AJAX调用服务器端的时候,可能会有错误(一般是网络错误)。当然也有可能是服务器端产生了一些错误,对于这些错误会,服务器会返回一个失败的响应并且附上错误消息给客户端。

  • 客户端代码应该处理这些错误,并且可以选择通知用户(可以显示一个错误对话框)。如果没有错误且服务器端返回了数据,客户端必须处理它。还有你应该限制页面的某个区域(或者整个页面),并显示一个忙碌的指示直到AJAX操作完成。

  • 服务器端在得到请求后执行服务器端代码,捕获异常并返回一个有效的响应给客户端。在错误情况下,可以选择发送错误消息给客户端。如果是验证错误,服务器端可以添加验证错误的验证信息。在成功情况下,可以发送返回值给客户端。

ABP的方式

由于使用 abp.ajax 函数对AJAX调用进行了封装, 所以ABP能自动化这些步骤。下面是一个AJAX调用示例:

var newPerson = {
    name: 'Dougles Adams',
    age: 42
};

abp.ajax({
    url: '/People/SavePerson',
    data: JSON.stringify(newPerson)
}).done(function(data) {
    abp.notify.success('created new person with id = ' + data.personId);
});

abp.ajax得到 options 作为对象。你可以传递任何有效的jQuery的 $.ajax 函数中的参数。有一些默认参数:dataType 是 json,type是 POST,还有 contentType是 application/json(在发送数据到服务器端之前,我们需要调用 JSON.stringify 将脚本对象转换为JSON字符串)。通过对apb.ajax传递options可以覆盖默认值。

abp.ajax返回promise。因此,你可以写这些处理函数:done,fail,then等等。在这个例子中,我们对 PeopleController's SavePerson action 发送了一个简单的AJAX请求。在 done 处理函数中,我们对新创建的person取得了它的主键id并且显示了创建成功的通知。让我们看看 MVC Controller

public class PeopleController : AbpController
{
    [HttpPost]
    public JsonResult SavePerson(SavePersonModel person)
    {
        //TODO: 保存新创建的person到数据库并且返回person的id
        return Json(new {PersonId = 42});
    }
}

正如你猜测的 SavePersonModel 包含了Name和Age属性。SavePerson 被标记为 HttpPost 特性,因为abp.ajax默认方法是POST。通过返回了匿名对象简化了方法实现。

这个看上去很简单直白,但是ABP在背后做了很多重要的处理。让我们深入了解一下:

AJAX 返回消息

即使我们直接的返回了一个带有PersonId = 2 的对象,ABP也会使用 MvcAjaxResponse 对象来包装它。事实上AJAX响应返回的内容应该像下面一样:

{
  "success": true,
  "result": {
    "personId": 42
  },
  "error": null,
  "targetUrl": null,
  "unAuthorizedRequest": false,
  "__abp": true
}

在这里所有的属性都是驼峰命名的(因为这在JavaScript中是惯例),即使在服务端代码中是PascalCased的。下面解释一下所有的字段:

  • success:boolean类型的值(true或者false),用来表示操作的成功状态。如果是ture,abp.ajax会解析该promise并且调用 done 函数。如果是false(在方法被调用的时候,如果有个异常被抛出),它会调用 fail 函数并且使用 abp.message.error 函数显示 error 消息。

  • result:控制器的action的实际返回值。如果success是ture并且服务器发送了返回值那么它才是有效的。

  • error:如果success是false,这个字段是一个包含 message和details 字段的对象。

  • targetUrl:如果需要的话,这提供了一种可能性:服务器端发送一个URL到客户端,使客户端可以重定向到其它的URL。

  • unAuthorizedRequest:这提供了一种可能性:服务器端发送通知给客户端该操作未被授权,或者是未认证用户。如果该值是true,那么abp.ajax会 reloads 当前的页面。

  • __abp:通过ABP包装响应返回的特殊签名。你自己不需要用到它,但是abp.ajax会处理它。

这种格式的对象会被 abp.ajax 函数识别且处理。abp.ajax会得到控制器的实际返回值(一个带有personid属性的对象),如果没有错误的话,那么你会在done函数中处理返回值。

处理错误

正如上面所述,ABP在服务器端处理异常,并且返回一个带有错误消息的对象。如下所示:

{
  "targetUrl": null,
  "result": null,
  "success": false,
  "error": {
    "message": "An internal error occured during your request!",
    "details": "..."
  },
  "unAuthorizedRequest": false,
  "__abp": true
}

正如你看到的,success是false 并且 result是null。abp.ajax处理这个对象,并且使用abp.message.error函数来显示错误消息给用户。如果你的服务器端代码抛出了 UserFriendlyException 类型的异常。它会直接的显示异常信息给用户。否则,它会隐藏实际的错误(将错误写入日志),并且显示一个标准的“服务器内部错误...”信息给用户。所有的这些都是ABP自动处理的。

你可能想为某个特别的AJAX调用禁止显示消息,那么添加 ** abpHandleError: false** 到 abp.ajax的options

HTTP状态码

在异常发生的时候,ABP会返回给定的HTTP状态码:

  • 401:未经身份验证的请求(没有登录,但是服务器端需要身份验证);

  • 403:未授权的请求;

  • 500:所有其它类型的异常。

6.6.2.5 WrapResult和DontWrapResult特性

使用 WrapResult和DontWrapResult 特性,可以对控制器的某个action或者所有的action来控制包装。

ASP.NET MVC 控制器

如果返回的类型是 JsonResult(或者Task<JsonResult>),那么ABP会默认包装ASP.NET MVC action的返回结果。你可以使用 WrapResult 特性来改变它。如下所示:

public class PeopleController : AbpController
{
    [HttpPost]
    [WrapResult(WrapOnSuccess = false, WrapOnError = false)]
    public JsonResult SavePerson(SavePersonModel person)
    {
        //TODO: 保存新创建的person到数据库并且返回person的id
        return Json(new {PersonId = 42});
    }
}

作为一个快速开发方式,我们只能使用 [DontWrapResult] 特性在这个相同的示例上。

你可以在启动配置里面改变这个默认的行为(使用 Configuration.Modules.AbpMvc()...)。

ASP.NET Web API 控制器

如果action被成功执行,ABP 不会默认包装 Web API Action的返回结果。如果需要的话,你可以添加WrapResult特性到action或者控制器上。但是它会 包装异常

你可以在启动配置里面改变这个默认的行为(使用 Configuration.Modules.AbpWebApi()...)。

动态Web API层

默认 ABP会 包装 动态Web API层的所有方法。你可以在你应用服务的接口上使用 WrapResult和DontWrapResult 特性来改变这个行为。

你可以在启动配置里面改变这个默认的行为(使用 Configuration.Modules.AbpWebApi()...)。

ASP.NET Core 控制器

ABP会自动包装JsonResult,ObjectRes以及那些没有实现IActionResult对象。详情请查阅ASP.NET Core文档

你可以在启动配置里面改变这个默认的行为(使用 using Configuration.Modules.AbpAspNetCore()...)。

动态Web API层

虽然ABP提供了一种调用Ajax的简单机制,但是在真实世界的应用中,为每个Ajax调用编写javascript函数是很经典的。例如:

//创建一个抽象了Ajax调用的function
var savePerson = function(person) {
    return abp.ajax({
        url: '/People/SavePerson',
        data: JSON.stringify(person)
    });
};

//创建一个新的 person
var newPerson = {
    name: 'Dougles Adams',
    age: 42
};

//保存该person
savePerson(newPerson).done(function(data) {
    abp.notify.success('created new person with id = ' + data.personId);
});

这是一个最佳实践,但是对每个AJAX调用函数都这样做,那是耗时且乏味的。对于应用服务和控制器,ABP能够自动的生成这些函数。

详情请阅读动态Web API层文档ASP.NET Core文档

Javascript Notification API

当一些事情发生的时候,我们喜欢显示一些别致的能够自动消失的通知,例如,当某个记录被保存或者某个问题发生的时候。ABP定义了标准的API实现了该功能。

abp.notify.success('a message text', 'optional title');
abp.notify.info('a message text', 'optional title');
abp.notify.warn('a message text', 'optional title');
abp.notify.error('a message text', 'optional title');

作为通知库的 自定义选项,它也能够取得第3个参数(对象)。

通知API默认是使用toastr库实现的。要使toastr生效,你应该引用toastr的css和javascript文件,然后再在页面中包含abp.toastr.js作为适配器。一个toastr成功通知如下所示:

image.png

你也可以用你最喜欢的通知库实现通知。只需要在自定义javascript文件中重写所有的函数,然后把它添加到页面中而不是abp.toastr.js中(你可以检查该文件看它是否实现,这个相当简单)。

abp.message简介

消息API被用来向用户显示一个消息或者从用户那里得到一个确认。

消息API默认实现方式是使用了sweetalert库。使用时你需要引用sweetalert的样式和js,然后把 abp.sweet-alert.js 作为适配器包含到你的页面中。

显示消息

如下所示:

abp.message.info('some info message', 'some optional title');
abp.message.success('some success message', 'some optional title');
abp.message.warn('some warning message', 'some optional title');
abp.message.error('some error message', 'some optional title');

成功的消息框显示如下:

image.png

Confirmation对话框

如下所示:

abp.message.confirm(
    'User admin will be deleted.', //确认提示
    'Are you sure?',//确认提示(可选参数)
    function (isConfirmed) {
        if (isConfirmed) {
            //...delete user 点击确认后执行
        }
    }
);

第二个参数(标题)是可选的(所以,回调函数可以作为第二个参数)。

确认消息框显示如下:

image.png

ABP在内部使用了消息API,例如:如果某个AJAX调用失败,那么它会调用abp.message.error。

Javascript UI Block & Busy API

ABP提供了有用的API,使整个页面或者页面的某个部分被遮罩层覆盖实现阻塞或者繁忙指示(使用加载图标表示繁忙)。

UI Block API

这个API使用一个透明的遮罩层(透明度可调节)来遮住整个页面或者该页面的某个元素。因此用户不能够点击。当你保存表单或者加载某个区域时(某个层或者整个页面),这是相当有用的。

如下所示:

abp.ui.block(); //遮住整个页面
abp.ui.block($('#MyDivElement')); //遮罩某个元素,在这里可以使用jQuery选择器选择元素..
abp.ui.block('#MyDivElement'); //..或者直接指定元素
abp.ui.unblock(); //解除遮罩
abp.ui.unblock('#MyDivElement'); //对指定元素解除遮罩

UI Block API 默认是使用jQuery插件block UI来实现的。为了能正常运行,你需要引用脚本文件,然后包含 abp.blockUI.js 文件作为适配器到你的页面中。

UI Busy API

该API被用来指示某些页面或者元素正在忙碌(加载)。例如:当你提交表单数据到服务器的时候,你可能想要遮罩这个表单并显示一个忙碌的指示器。

如下所示:

abp.ui.setBusy('#MyLoginForm');
abp.ui.clearBusy('#MyLoginForm');
image.png

参数应该是一个jQuery选择器(如:#MyLoginForm)或者jQuery对象(如:$('#MyLoginForm'))。为了使整个页面都是在繁忙状态,你应该传递null或者body作为选择器。

setBusy函数能够传入一个promise(作为第二个参数)并且自动的清除busy,当该promise完成的时候。如下所示:

abp.ui.setBusy(
    $('#MyLoginForm'), 
    abp.ajax({ ... })   
);

由于abp.ajax返回的是promise,所以我们能直接使用它作为参数。如果你想了解更多关于promise的资料,请查阅jQuery的Deferred。setBusy对Q提供支持(以及angulars的$http服务)。

UI Busy API 使用spin.js实现的。为使其正常运行,你应该引用该脚本文件,然后在你的页面中包含 abp.spin.js 作为适配器。

ABP表现层 - 事件总线EventBUS

简介

Pub/Sub 事件模型被广泛的应用在客户端。ABP包含了一个 简单的全局事件总线 用来注册事件并且触发事件。

注册事件

你可以使用 abp.event.on注册 一个 全局事件 。示例如下:

abp.event.on('itemAddedToBasket', function (item) {
    console.log(item.name + ' is added to basket!');
});

第一个参数是 该事件的唯一名称。另一个参数是 回调函数,当指定的事件被触发后将调用该参数。

你可以使用 abp.event.off 方法来 卸载 已注册的事件。
注意:为了能够卸载指定的事件,应该提供相同的事件函数。
正如上面的示例所展示的,你应该将回调函数设置为一个变量,然后在 on和off 中使用它。

触发事件

abp.event.trigger 被用来触发全局事件。触发一个已注册的事件的代码如下:

abp.event.trigger('itemAddedToBasket', {
    id: 42,
    name: 'Acme Light MousePad'
});

第一个参数是 该事件的唯一名称。第二个是(可选的)事件参数。你可以添加任何数量的参数,并且在回调方法中获得它们。

ABP表现层 - Javascript 日志 API

简介

当你想要在客户端写一些简单的日志的时候,你可以使用 console.log('...') API。但是,它不是所有的浏览器都支持该API,并且该函数也可能破坏你的脚本。所以,在使用的时候你首先应该检查console是否有效。还有,你可能想在其它地方写日志。甚至你可能对写日志的等级也有要求。ABP定义了安全的日志函数:

abp.log.debug('...');
abp.log.info('...');
abp.log.warn('...');
abp.log.error('...');
abp.log.fatal('...');

你可以通过设置 abp.log.levelabp.log.levels 中的某个日志等级进行更改(例如:abp.log.levels.INFO 不会记录调试日志)。这些函数默认将日志记录到了浏览器的控制台里了。但如果你需要的话,你也可以重写或者扩展这个行为。

ABP表现层 - 其他工具函数OtherUtilities

ABP提供了一些通用的工具函数。

abp.utils.createNamespace

用于创建更深的命名空间。假设我们有一个基命名空间 abp,然后想要创建或者获得 abp.utils.strings.formatting 命名空间。不需要像下面这样写:

//创建或获得namespace
abp.utils = abp.utils || {};
abp.utils.strings = abp.utils.strings || {};
abp.utils.strings.formatting = abp.utils.strings.formatting || {};

//给该namespace添加一个function
abp.utils.strings.formatting.format = function() { ... };

我们可以这样写:

var formatting = abp.utils.createNamespace(abp, 'utils.strings.formatting');

//给该namespace添加一个function
formatting.format = function() { ... };

这样即安全又简单的创建了更深层次的命名空间。注意,第一个参数是必须存在的根命名空间。

abp.utils.formatString

近似于C#中的string.Format()方法。示例如下:

var str = abp.utils.formatString('Hello {0}!', 'World'); //str = 'Hello World!'
var str = abp.utils.formatString('{0} number is {1}.', 'Secret', 42); //str = 'Secret number is 42'

返回简书总目录:ABP+AdminLTE+Bootstrap Table权限管理系统一期
前往博客园总目录:ABP+AdminLTE+Bootstrap Table权限管理系统一期

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