MiddleWares

MiddleWares

MiddleWares 的概述

在C# 中,ASP.NET Core 应用程序的处理流程通过一个称为管道(pipeline)的概念来组织,而中间件就是在这个管道中起作用的一部分,中间件是一种装配到应用管道以处理HTTP 请求和响应的软件,每个组件:

  • 选择是否将请求传递到管道中的下一个组件
  • 可在管道中的下一个组件前后执行工作

MiddleWares 的工作原理

中间件按照配置的顺序组成一个管道,处理传入的 HTTP 请求并生成相应的 HTTP 响应:

  1. 请求流向: 当 ASP.NET Core 应用程序接收到一个 HTTP 请求时,请求会被传递到应用程序的中间件管道
  2. 中间件管道: 中间件管道是由一系列中间件组成的,它们按照在 Startup 类的 Configure 方法中的配置顺序执行
  3. 中间件处理:每个中间件都有一个机会处理请求,中间件通过实现 Invoke 方法(或 InvokeAsync 方法)来执行自己的逻辑,Invoke 方法接收一个 HttpContext 对象 和 一个 Func<Task> (表示下一个中间件或终端中间件的委托)
    • 请求委托由一系列中间件组成,每个中间件都负责对请求进行一些处理
    • 请求委托用于生成请求管道,处理每个 HTTP 请求
public async Task InvokeAsync(HttpContext context, Func<Task> next)
{
    // 在中间件执行前的逻辑

    await next(); // 调用下一个中间件或终端中间件

    // 在中间件执行后的逻辑
}
  1. 传递请求: 每个中间件可以选择将请求传递给下一个中间件,也可以决定短路管道,不将请求传递给下一个中间件
  2. 异步处理:中间件通常是异步执行的,可以执行 异步操作 并 等待 其他中间件 完成后 继续执行
  3. 生成响应: 终端中间件或最后一个中间件负责生成 HTTP 响应,若中间件短路,请求将在到达 终端中间件 之前结束,终端中间件生成响应并 将其发送回客户端
  4. HttpContext: HttpContext 对象是中间件工作的上下文,包含有关的请求和响应信息,以及一些有关当前执行环境的其他信息
image.png

使用中间件的具体流程:

  1. 异常/错误处理
    • 当应用在开发环境中运行时:
    • 当应用在生产环境中运行时:
      • 异常处理程序中间件 (UseExceptionHandler) 捕获以下中间件中引发的异常
      • HTTP 严格传输安全协议 (HSTS) 中间件 (UseHsts) 添加 Strict-Transport-Security 标头
  2. HTTPS 重定向中间件 (UseHttpsRedirection) 将 HTTP 请求重定向到 HTTPS
  3. 静态文件中间件 (UseStaticFiles) 返回静态文件,并简化进一步请求处理
  4. Cookie 策略中间件 (UseCookiePolicy) 使应用符合欧盟一般数据保护条例 (GDPR) 规定
  5. 用于路由请求的路由中间件 (UseRouting)
  6. 身份验证中间件 (UseAuthentication) 尝试对用户进行身份验证,然后才会允许用户访问安全资源
  7. 用于授权用户访问安全资源的授权中间件 (UseAuthorization)
  8. 会话中间件 (UseSession) 建立和维护会话状态。 如果应用使用会话状态,请在 Cookie 策略中间件之后和 MVC 中间件之前调用会话中间件
  9. 用于将 Razor Pages 终结点添加到请求管道的终结点路由中间件(带有 MapRazorPagesUseEndpoints
image.png

⚠️ 注意:

  • 每个委托均可在下一个委托前后执行操作,应尽早在管道中 调用处理委托,以便捕获在管道后期阶段发生的异常
  • 当委托不将请求传递给下一个委托时,称为“为请求管道让路”,这表示当前中间件已经处理了请求并生成了相应的响应,不需要将请求传递给管道中的后续中间件,通常需要短路,可以避免不必要的工作
    • 在向客户端发送响应后,请勿调用 next.Invoke。 响应启动后,针对 HttpResponse 的更改将引发异常

MiddleWares 的种类

在 ASP.NET Core 中,有许多内置的中间件和社区提供的中间件,可以用于处理不同方面的请求和响应。以下是一些常见的中间件类型:

静态文件中间件(UseStaticFiles)

静态文件中间件 是用于提供静态文件,如CSS、JavaScript和图像,而无需借助额外的服务器或处理逻辑

  1. 配置静态文件中间件: 在 Startup 类的 Configure 方法中配置静态文件中间件,使用 UseStaticFiles 方法启动该 中间件
public void Configure(IApplicationBuilder app)
{
    app.UseStaticFiles();
    // 其他中间件配置...
}
  1. 默认文件: 静态文件中间件运行指定默认的文件,当请求的路径是一个目录时,中间件将尝试返回默认文件
app.UseDefaultFiles();
  1. 自定义静态文件路径:可以通过 UseStaticFiles 方法的参数指定静态文件的路径,默认情况下,中间件将在应用程序的根目录中查找静态文件
app.UseStaticFiles(new StaticFileOptions
{
    // 静态文件将从应用程序根目录下的 "MyStaticFiles" 文件夹提供,并且在 URL 中使用 "/static" 前缀
    FileProvider = new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), "MyStaticFiles")),
    RequestPath = "/static"
});
  1. 缓存控制: 静态文件中间件还提供缓存控制的选项,可以配置浏览器缓存策略
app.UseStaticFiles(new StaticFileOptions
{
    OnPrepareResponse = context =>
    {
        // 设置缓存策略, max-age=604800 表示浏览器将缓存文件一周
        context.Context.Response.Headers.Append("Cache-Control", "public, max-age=604800");
    }
});
  1. 启用 目录 浏览: 静态文件中间件还可以启用目录浏览,以便查看目录中的文件
app.UseDirectoryBrowser();

身份验证中间件 (UseAuthentication)

身份验证中间件是 ASP.NET Core 中用于 处理用户身份验证的关键组件,负责验证用户的身份,并在用户提供认证是生成相应的身份信息

  1. 配置身份验证中间件: 在 Startup 类的 ConfigureServices 的方法中配置身份验证服务,使用 AddAuthentication 方法来启用身份验证
public void ConfigureServices(IServiceCollection services)
{
    services.AddAuthentication(options =>
    {
        options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    })
    .AddCookie(options =>
    {
        options.Cookie.Name = "YourAuthCookieName"; // 使用 Cookie 身份验证方案,并指定配置
        // 配置其他选项...
    });

    // 其他服务配置...
}
  1. 在中间件管道中使用身份验证: 在 Configure 方法中将 身份验证中间件添加到中间件管道中,通常放在 UseRouting 和 UseEndpoints 之间
public void Configure(IApplicationBuilder app)
{
    app.UseRouting();

    app.UseAuthentication();
    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        // 配置终结点...
    });
}
  1. 添加身份验证控制器: 在需要进行身份验证的控制器 或 动作 上 添加 [Authorize] 属性,以确保只有经过身份验证的用户才能访问
[Authorize]
public class SecureController : Controller
{
    // 受身份验证保护的操作...
}
  1. 登录和注销:身份验证中间件 还提供了一些方法用于登录和注销用户
[HttpPost]
public async Task<IActionResult> Login(LoginViewModel model)
{
    // 在控制器中可以使用 SignInManager 来登录用户
    var result = await _signInManager.PasswordSignInAsync(model.UserName, model.Password, model.RememberMe, lockoutOnFailure: false);
    
    if (result.Succeeded)
    {
        // 登录成功的处理逻辑
        return RedirectToAction("Index", "Home");
    }
    else
    {
        // 登录失败的处理逻辑
        ModelState.AddModelError(string.Empty, "Invalid login attempt.");
        return View(model);
    }
}

// 注销用户可以使用 SignOut
[HttpPost]
public async Task<IActionResult> Logout()
{
    await _signInManager.SignOutAsync();
    return RedirectToAction("Index", "Home");
}

授权中间件 (UseAuthorization)

授权中间件 是 ASP.NET Core中用于处理 用户授权的关键组件,负责检查用户是否具体访问受保护资源的权限

  1. 配置授权中间件:在 startup 类中 ConfigureServices 方法中配置授权服务,使用 AddAuthorization 方法来启用授权
public void ConfigureServices(IServiceCollection services)
{
    services.AddAuthorization(options =>
    {
        // 使用 AddPolicy 方法添加了一个名为 "RequireAdminRole" 的授权策略,该策略要求用户具有 "Admin" 角色
        options.AddPolicy("RequireAdminRole", policy => policy.RequireRole("Admin"));
        // 添加其他授权策略...
    });

    // 其他服务配置...
}
  1. 在中间件管道中使用授权:在 Configure 方法中将授权中间件到中间件管道中,通常放在 UseRouting 和 UseEndpoints 之间
public void Configure(IApplicationBuilder app)
{
    app.UseRouting();

    app.UseAuthentication();
    app.UseAuthorization();  // 用于启用授权中间件

    app.UseEndpoints(endpoints =>
    {
        // 配置终结点...
    });
}
  1. 在控制器或动作上 应用授权策略: 在需要进行授权的控制器或动作上 使用 [Authorize] 属性,并指定要应用的授权策略
[Authorize("RequireAdminRole")]
public class AdminController : Controller
{
    // 需要 "Admin" 角色才能访问的操作...
}
  1. 自定义授权策略: 可自定义授权策略
// 自定义了一个授权策略 "RequireCustomClaim",要求用户具有名为 "CustomClaim" 且值为 "Allowed" 的声明
services.AddAuthorization(options =>
{
    options.AddPolicy("RequireCustomClaim", policy =>
        policy.RequireAssertion(context =>
            context.User.HasClaim(c => c.Type == "CustomClaim" && c.Value == "Allowed")));
});

路由中间件 (UseRouting)

路由中间件是用于处理 HTTP 请求并将其路由到相应的处理程序(通常是控制器中的动作方法)的关键组件

  1. 配置路由中间件:在 Startup 类的 Configure 方法中 配置路由中间件,通常,路由中间件会在 UseRouting 和 UseEndpoints 之间进行配置
public void Configure(IApplicationBuilder app)
{
    app.UseRouting();

    // 配置路由中间件
    app.UseEndpoints(endpoints =>
    {
        // 配置了一个默认的控制器路由,并将请求路由到名为 default 的路由规则
        endpoints.MapControllerRoute(
            name: "default",
            pattern: "{controller=Home}/{action=Index}/{id?}");
    });
}
  1. 路由参数: 路由中间件支持路由参数,可以通过占位符在路由模版中指定
// 示例路由模板, {controller}、{action}、{id} 都是常见的路由参数
"{controller=Home}/{action=Index}/{id?}"
  1. Attribute 路由:除了可以在 Startup 中配置路由规则外,还可以使用控制器和动作方法上的 Route 特性进行路由配置
[Route("custom/[controller]")]
public class MyController : Controller
{
    // Route 特性指定了自定义的控制器和动作方法的路由
    [Route("custom-action/{id}")]
    public IActionResult MyAction(int id)
    {
        // 动作方法的逻辑...
    }
}
  1. 路由约束:路由中间件还支持路由约束,以限制参数的匹配规则
// 数字约束
"{id:int}"

// 字符串约束
"{name:minlength(3)}"

端点中间件 (UseEndpoints)

端点中间件通常指的是在请求处理管道的最后阶段,通过 UseEndpoints 方法配置的中间件,这个中间件负责处理传入的 HTTP 请求并路由到相应的控制器和动作方法

  1. 配置端点中间件:在 Startup 类 的 Configure 方法中,使用 UseEndpoints 方法配置 端点中间件,通常是在路由中间件之后配置的
public void Configure(IApplicationBuilder app)
{
    app.UseRouting();

    app.UseEndpoints(endpoints =>
    {
        // 配置控制器和动作方法的路由
        endpoints.MapControllerRoute(
            name: "default",
            pattern: "{controller=Home}/{action=Index}/{id?}");
        
        // 配置端点中间件 :配置了一个回退路由,将请求路由 到 Home 控制器的 Index 的动作方法
        endpoints.MapFallbackToController("Index", "Home");
    });
}
  1. 路由参数和约束:端点中间件支持与 路由相关的参数和约束,可以在 MapControllerRoute 方法中配置
// 定义了一个名为 custom 路由,指定了控制器、动作方法和一个可选的整数类型的参数
endpoints.MapControllerRoute(
    name: "custom",
    pattern: "{controller=Custom}/{action=Index}/{id:int?}");
  1. MapFallback 方法: 除了 MapFallbackToController ,还可以使用 MapFallBack 方法配置自定义的回退处理 逻辑
endpoints.MapFallback(context => 
{
    // 自定义的回退处理逻辑
    context.Response.WriteAsync("Fallback response");
});

错误处理中间件

错误处理中间件 是 用于捕获应用程序中发生的异常并提供用户友好错误的页面或日志记录的中间件

  1. 配置错误处理 中间件:在 Startup 类的 Configure 方法中配置 错误 处理 中间件,通常 错误 处理中间件 会在其他中间件之后配置
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage(); // 在开发环境中使用开发者异常页面
    }
    else
    {
        app.UseExceptionHandler("/Home/Error"); // 在生产环境中使用自定义错误处理页面
        app.UseStatusCodePagesWithReExecute("/Home/Error/{0}"); // 在生产环境中使用状态码页面
    }

    // 其他中间件配置...
}
  1. 自定义错误页面: 可以为不同的 HTTP 状态码 配置 自定义 的错误页面
// 使用 UseStatusCodePagesWithReExecute 来为不同的状态码配置自定义的错误页面
app.UseStatusCodePagesWithReExecute("/Home/CustomError/{0}");
  1. 开发者异常页面: 在开发环境中,可以使用 UseDeveloperExceptionPage 来显示详细的开发者异常页面,包含堆栈跟踪等信息
app.UseDeveloperExceptionPage();

Session 中间件 (UseSession)

Session 中间件用于 在应用程序中启用和管理 会话状态,会话状态允许在用户的多个请求之间存储和检索数据

  1. 配置 Session 中间件: 在 Startup 类的 ConfigureServices 方法中 配置 Session 服务 ,并在
    Configure 方法中启用 Session 中间件
public void ConfigureServices(IServiceCollection services)
{
    services.AddSession(options =>
    {
        // 配置Session选项
        options.IdleTimeout = TimeSpan.FromMinutes(20);
    });

    // 其他服务配置...
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app.UseSession(); // 启用Session中间件

    // 其他中间件配置...
}
  1. 在控制器中 使用 Session:可以在控制器 或视图 中 使用 HttpContext.Session 属性来存储和检索会话数据
// 写入Session数据
HttpContext.Session.SetString("UserName", "John");

// 读取Session数据
var userName = HttpContext.Session.GetString("UserName");
  1. 配置 Session 选项: 通过 AddSession 方法 的参数配置 Session 选项
services.AddSession(options =>
{
    // 设置了会话的空闲超时时间为20分钟
    options.IdleTimeout = TimeSpan.FromMinutes(20);
});
  1. 注意事项
  • Session 中间件依赖于底层的数据存储,在开发环境中,可以使用内存存储;在生产环境中,建议使用分布式缓存或数据库等可扩展的存储方式
  • 要使用 Session 中间件,需要在应用程序中 启用 Cookies 中间件,因为 Session 数据通常存储在客户端的 Cookie 中

跨域资源共享 (CORS) 中间件 (UseCors)

跨域资源共享CORS 中间件 是用于处理 跨域请求的组件,允许在不同域 的客户端发起对应用程序 的请求,通过配置服务器响应头 来授权跨域请求,通过配置 CORS 中间件,可以控制应用程序对跨域请求的响应,提供安全的跨域资源的共享机制,防止恶意网站利用资源

  1. 配置 CORS 中间件: 在 Startup 类的 ConfigureServices 方法中 配置 CORS 服务,并在 Configure 方法中启用 CORS 中间件
public void ConfigureServices(IServiceCollection services)
{
    services.AddCors(options =>
    {
        options.AddPolicy("AllowSpecificOrigin",
            builder => builder.WithOrigins("https://example.com"));
    });

    // 其他服务配置...
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app.UseCors("AllowSpecificOrigin"); // 启用CORS中间件

    // 其他中间件配置...
}
  1. CORS 策略配置: 可以配置多个 CORS 策略,并为每个策略指定允许的来源、允许的方法、允许的标头等
services.AddCors(options =>
{
    options.AddPolicy("AllowSpecificOrigin",
        builder => builder
            .WithOrigins("https://example.com")
            .AllowAnyMethod()
            .AllowAnyHeader());
    
    options.AddPolicy("AllowAllOrigins",
        builder => builder
            .AllowAnyOrigin()
            .AllowAnyMethod()
            .AllowAnyHeader());
});
  1. 全局CORS 配置: 可以使用全局配置允许所有 请求跨域,在生产环境中要慎重使用
services.AddCors(options =>
{
    options.AddPolicy("AllowAll",
        builder => builder
            .AllowAnyOrigin()
            .AllowAnyMethod()
            .AllowAnyHeader());
});
  1. 在控制器 中 使用 CORS: 可以在控制器或动作方法上使用 [EnableCors] 特性来覆盖全局CORS 配置
[EnableCors("AllowSpecificOrigin")]
public class MyController : Controller
{
    // 控制器的逻辑...
}

MVC 中间件 (UseMvc)

MVC (Model-View-Controller) 中间件 是用于构建 Web 应用程序的核心组件。MVC 模式 将应用程序分为 模型 Model、视图 View 和 控制器 Controller 三个部分,通过中间件实现了 请求的路由、处理、和响应

  1. 配置 MVC 中间件: 在 Startup 类的 ConfigureServices 方法中 配置 MVC 服务,并在 Configure 方法中 启用 MVC 中间件
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc(); // 配置MVC服务

    // 其他服务配置...
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app.UseMvc(); // 启用MVC中间件

    // 其他中间件配置...
}
  1. MVC 路由: MVC 中间件通过路由来映射 HTTP 请求到相应的控制器和动作方法
// Route 特性定义了控制器和动作方法的路由
[Route("api/[controller]")]
public class MyController : Controller
{
    [HttpGet]
    public IActionResult Get()
    {
        // 处理GET请求的逻辑...
    }
}
  1. 控制器和动作方法:控制器是处理 HTTP 请求的主要组件,动作方法是控制器中实际执行的逻辑
public class MyController : Controller
{
    public IActionResult Index()
    {
        return View();
    }
}
  1. 视图:视图用于渲染用户界面,通常与控制器中的动作方法相关联
public IActionResult Index()
{
    return View();
}

日志记录中间件

日志记录中间件并不是 一个单独的中间件,而是通过内置的日志记录提供程序来实现的,ASP.NET Core 内置了丰富的日志记录功能,可以通过配置使用不同的提供程序,如Console、Debug、EventSource、File、Seq 等,实现应用程序中 的 日志记录,监视应用程序的行为和性能

  1. 配置日志记录服务:在 Startup 类的 ConfigureServices 方法中 配置日志记录服务
public void ConfigureServices(IServiceCollection services)
{
    services.AddLogging(); // 配置日志记录服务

    // 其他服务配置...
}
  1. 使用日志的记录:在控制器、中间件、或其他服务中使用依赖注入来获取 ILogger<T> 实例,T 是 包含日志记录的类名
public class MyService
{
    private readonly ILogger<MyService> _logger;

    // 通过 构造函数注入 ILogger<MyService> 实例
    public MyService(ILogger<MyService> logger)
    {
        _logger = logger;
    }

    public void DoSomething()
    {
        // 然后使用 LogInformation 方法记录信息日志
        _logger.LogInformation("Doing something...");
    }
}
  1. 配置日志级别: 可以通过配置来设置日志记录的最低级别,以便过滤低于该级别的日志
public void ConfigureLogging(ILoggingBuilder logging)
{
    logging.SetMinimumLevel(LogLevel.Information); // 通过 SetMinimumLevel 方法设置了日志记录的最低级别为 Information
    logging.AddConsole(); // 使用Console日志记录提供程序
}
  1. 使用其他提供程序: 除了 Console,还可以使用其他日志记录提供程序,如 File、EventSource、Seq 等
public void ConfigureLogging(ILoggingBuilder logging)
{
    logging.AddFile("logs/mylog.txt"); // 使用File日志记录提供程序
    logging.AddEventSourceLogger(); // 使用EventSource日志记录提供程序
    logging.AddSeq("http://seq-server"); // 使用Seq日志记录提供程序
}

配置 中间件

Map

Map 用于根据 请求的路径或其他条件将 请求 路由到 指定的分支中

// 如果请求路径为"/path",则会执行 Run 方法中 的逻辑
app.Map("/path", branch => 
{
    branch.Run(async context =>
    {
        await context.Response.WriteAsync("Mapped Path");
    });
});

Run

Run 用于指定在请求处理管道中的某一点执行的逻辑,通常用于终端中间件,终端中间件只会在请求流水线中执行一次,并且不会将请求传递给下一个中间件,即不再调用管道中的下一个中间件

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello from the terminal middleware!");
});

Use

Use 用于添加中间件到请求处理管道中,可以用于执行一些逻辑,并将请求传递给下一个中间件

app.Use(async (context, next) =>
{
    // 在中间件之前执行一些操作
    await context.Response.WriteAsync("Before next middleware");

    // 将请求传递给下一个中间件
    await next.Invoke();

    // 在中间件之后执行一些操作
    await context.Response.WriteAsync("After next middleware");
});

app.Run(async context =>
{
    await context.Response.WriteAsync("End of pipeline");
});
  • Map 用于分支路由
  • Run 用于终端中间件
  • Use 用于添加一般的中间件

中间件: https://learn.microsoft.com/zh-cn/aspnet/core/fundamentals/middleware/?view=aspnetcore-8.0

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

推荐阅读更多精彩内容