精华内容
下载资源
问答
  • ASP.NET Core MVC 中的模型验证

    千次阅读 2018-11-27 16:01:22
    数据模型的验证被视为是数据合法性的第一,要求满足类型、长度、校验等规则,有了MVC的模型校验能够省却很多前后端代码,为代码的简洁性也做出了不少贡献。 原文地址:...

    数据模型的验证被视为是数据合法性的第一步,要求满足类型、长度、校验等规则,有了MVC的模型校验能够省却很多前后端代码,为代码的简洁性也做出了不少贡献。

    原文地址:https://docs.microsoft.com/zh-cn/aspnet/core/mvc/models/validation?view=aspnetcore-2.1

    作者:Rachel Appel

    模型验证简介

    在将数据存储到数据库之前,应用必须先验证数据。 必须检查数据是否存在潜在的安全威胁,确保数据已设置适当的类型和大小格式,并且必须符合相关规则。 实施验证的过程可能有些单调乏味,但却必不可少。 在 MVC 中,验证发生在客户端和服务器上。

    幸运的是,.NET 已将验证抽象化为验证属性。 这些属性包含验证代码,从而减少了所需编写的代码量。

    在 ASP.NET Core 2.2 及更高版本中,如果能够确定给定模型关系图不需要进行验证,ASP.NET Core 运行时便会简化(跳过)验证。 验证无法或没有关联任何验证程序的模型时,跳过验证可能会显著提升性能。 已跳过的验证包括诸如基元集合(byte[]string[]Dictionary<string, string> 等)之类的对象,或没有任何验证程序的复杂对象关系图。

    查看或下载 GitHub 中的示例

    验证属性

    验证属性用于配置模型验证,因此,在概念上类似于数据库表中字段上的验证。 它包括诸如分配数据类型或必填字段之类的约束。 其他类型的验证包括将向数据应用模式以强制实施业务规则,比如信用卡、电话号码或电子邮件地址。 验证属性更易使用,并使这些要求的实施变得更简单。

    验证特性在属性级别指定:

    C#

    [Required]
    public string MyProperty { get; set; } 
    

    下面是一个应用的已批注 Movie 模型,该应用用于存储电影和电视节目的相关信息。 大多数属性都是必需属性,多个字符串属性具有长度要求。 此外,还有一个针对·Price 属性设置的从 0 到 $999.99 的数值范围限制,以及一个自定义验证特性。

    C#

    public class Movie
    {
        public int Id { get; set; }
    
        [Required]
        [StringLength(100)]
        public string Title { get; set; }
    
        [ClassicMovie(1960)]
        [DataType(DataType.Date)]
        public DateTime ReleaseDate { get; set; }
    
        [Required]
        [StringLength(1000)]
        public string Description { get; set; }
    
        [Range(0, 999.99)]
        public decimal Price { get; set; }
    
        [Required]
        public Genre Genre { get; set; }
    
        public bool Preorder { get; set; }
    }
    

    通过读取整个模型即可显示有关此应用的数据的规则,从而使代码维护变得更轻松。 下面是几个常用的内置验证属性:

    • [CreditCard]:验证属性是否具有信用卡格式。

    • [Compare]:验证某个模型中的两个属性是否匹配。

    • [EmailAddress]:验证属性是否具有电子邮件格式。

    • [Phone]:验证属性是否具有电话格式。

    • [Range]:验证属性值是否落在给定范围内。

    • [RegularExpression]:验证数据是否与指定的正则表达式匹配。

    • [Required]:将属性设置为必需属性。

    • [StringLength]:验证字符串属性是否最多具有给定的最大长度。

    • [Url]:验证属性是否具有 URL 格式。

    MVC 支持从 ValidationAttribute 派生的所有用于验证的属性。 在 System.ComponentModel.DataAnnotations 命名空间中可找到许多有用的验证属性。

    在某些情况下,内置属性可能无法提供所需的功能。 这时,就可以通过从 ValidationAttribute 派生或将模型更改为实现 IValidatableObject,来创建自定义验证属性。

    必需属性的使用说明

    从本质上来说,需要不可以为 null 的值类型(如 decimalintfloatDateTime),但不需要 Required 特性。 应用不会对标记为 Required 的不可为 null 的类型执行任何服务器端验证检查。

    对于不可为 null 的类型,MVC 模型绑定(与验证和验证属性无关)会拒绝包含缺失值或空白的表单域提交。 如果目标属性上缺少 BindRequired 特性,模型绑定会忽略不可为 null 的类型的缺失数据,导致传入表单数据中缺少表单域。

    BindRequired 特性(另请参阅 ASP.NET Core 中的模型绑定)可用于确保表单数据完整。 当应用于某个属性时,模型绑定系统要求该属性具有值。 当应用于某个类型时,模型绑定系统要求该类型的所有属性都具有值。

    使用 Nullable<T> 类型(例如,decimal?System.Nullable<decimal>)并将其标记为 Required 时,将执行服务器端验证检查,就像该属性是标准的可以为 null 的类型(例如,string)一样。

    客户端验证要求与标记为 Required 的模型属性对应的表单域以及未标记为 Required 的不可为 null 的类型属性具有值。 Required 可用于控制客户端验证错误消息。

    模型状态

    模型状态表示已提交的 HTML 表单值中的验证错误。

    MVC 将继续验证字段,直至达到错误数上限(默认为 200 个)。 可以使用 Startup.ConfigureServices 中的以下代码配置该数字:

    C#

    services.AddMvc(options => options.MaxModelValidationErrors = 50);
    

    处理模型状态错误

    在执行控制器操作之前进行模型验证。 该操作负责检查 ModelState.IsValid 并做出相应响应。 在许多情况下,正确的反应是返回错误响应,理想状况下会详细说明模型验证失败的原因。

    如果在使用 [ApiController] 属性的 web API 控制器中,ModelState.IsValid 的计算结果为 false,将返回包含问题详细信息的自动 HTTP 400 响应。 有关详细信息,请参阅自动 HTTP 400 响应

    某些应用会选择遵循标准约定来处理模型验证错误,在这种情况下,可以在筛选器中实现此类策略。 应测试操作在有效模型状态和无效模型状态下的行为方式。

    手动验证

    完成模型绑定和验证后,可能需要重复其中的某些步骤。 例如,用户可能在应输入整数的字段中输入了文本,或者你可能需要计算模型的某个属性的值。

    你可能需要手动运行验证。 为此,请调用 TryValidateModel 方法,如下所示:

    C#

    TryValidateModel(movie);
    

    自定义验证

    验证属性适用于大多数验证需求。 但是,某些验证规则特定于你的业务。 你的规则可能不是常见的数据验证技术,比如确保字段是必填字段或符合一系列值。 在这些情况下,自定义验证属性是一种不错的解决方案。 在 MVC 中创建你自己的自定义验证属性很简单。 只需从 ValidationAttribute 继承并重写 IsValid 方法。 IsValid 方法采用两个参数,第一个是名为 value 的对象,第二个是名为 validationContextValidationContext 对象。 Value 引用自定义验证程序要验证的字段中的实际值。

    在下面的示例中,一项业务规则规定,用户不能将 1960 年以后发行的电影的流派设置为 Classic[ClassicMovie] 属性会先检查流派,如果是经典流派,则查看发行日期是否晚于 1960 年。 如果晚于 1960 年,则验证失败。 此属性采用一个表示年份的整数参数,可用于验证数据。 可以在该属性的构造函数中捕获该参数的值,如下所示:

    C#

    public class ClassicMovieAttribute : ValidationAttribute, IClientModelValidator
    {
        private int _year;
    
        public ClassicMovieAttribute(int year)
        {
            _year = year;
        }
    
        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            Movie movie = (Movie)validationContext.ObjectInstance;
    
            if (movie.Genre == Genre.Classic && movie.ReleaseDate.Year > _year)
            {
                return new ValidationResult(GetErrorMessage());
            }
    
            return ValidationResult.Success;
        }
    

    上面的 movie 变量表示一个 Movie 对象,其中包含要验证的表单提交中的数据。 在此例中,验证代码会根据规则检查 ClassicMovieAttribute 类的 IsValid 方法中的日期和流派。 验证成功时,IsValid 返回 ValidationResult.Success 代码。 验证失败时,返回 ValidationResult 和错误消息:

    C#

    private string GetErrorMessage()
    {
        return $"Classic movies must have a release year earlier than {_year}.";
    }
    

    当用户修改 Genre 字段并提交表单时,ClassicMovieAttributeIsValid 方法将验证该电影是否为经典电影。 将 ClassicMovieAttribute 像所有内置特性一样应用于属性(如 ReleaseDate)以确保执行验证,如前面的代码示例所示。 由于此示例仅适用于 Movie 类型,因此建议使用 IValidatableObject,如下一段中所示。

    也可以通过实现 IValidatableObject 接口上的 Validate 方法,将这段代码直接放入模型中。 如果自定义验证特性可用于验证各个属性,则可使用 IValidatableObject 来实现类级别的验证,如下所示。

    C#

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (Genre == Genre.Classic && ReleaseDate.Year > _classicYear)
        {
            yield return new ValidationResult(
                $"Classic movies must have a release year earlier than {_classicYear}.",
                new[] { "ReleaseDate" });
        }
    }
    

    客户端验证

    客户端验证极大地方便了用户。 它节省了时间,让用户不必浪费时间等待服务器往返。 从商业角度而言,即使每次只有几分之一秒,但如果每天有几百次,也会耗费大量的时间和成本,带来很多不必要的烦恼。 简单直接的验证能够提高用户的工作效率和投入产出比。

    你必须有一个包含适当的 JavaScript 脚本引用的视图,才能让客户端验证正常工作,如下所示。

    CSHTML

    <script src="https://ajax.aspnetcdn.com/ajax/jQuery/jquery-2.2.0.min.js"></script>
    

    CSHTML

    <script src="https://ajax.aspnetcdn.com/ajax/jquery.validate/1.16.0/jquery.validate.min.js"></script>
    <script src="https://ajax.aspnetcdn.com/ajax/jquery.validation.unobtrusive/3.2.6/jquery.validate.unobtrusive.min.js"></script>
    

    jQuery 非介入式验证脚本是一个基于热门 jQuery Validate 插件的自定义 Microsoft 前端库。 如果没有 jQuery 非介入式验证,则必须在两个位置编码相同的验证逻辑:一次是在模型属性上的服务器端验证特性中,一次是在客户端脚本中(jQuery Validate 的 validate() 方法示例展示了这种情况可能的复杂程度)。 MVC 的标记帮助程序HTML 帮助程序则能够使用模型属性中的验证特性和类型元数据,呈现需要验证的表单元素中的 HTML 5 data- 特性。 MVC 为内置属性和自定义属性生成 data- 属性。 然后,jQuery 非介入式验证分析 data- 属性并将逻辑传递给 jQuery Validate,从而将服务器端验证逻辑有效地“复制”到客户端。 可以使用相关标记帮助程序在客户端上显示验证错误,如下所示:

    CSHTML

    <div class="form-group">
        <label asp-for="ReleaseDate" class="col-md-2 control-label"></label>
        <div class="col-md-10">
            <input asp-for="ReleaseDate" class="form-control" />
            <span asp-validation-for="ReleaseDate" class="text-danger"></span>
        </div>
    </div>
    

    上面的标记帮助程序将呈现以下 HTML。 请注意,HTML 输出中的 data- 特性与 ReleaseDate 属性的验证特性相对应。 下面的 data-val-required 属性包含在用户未填写发行日期字段时将显示的错误消息。 jQuery 非介入式验证将此值传递给 jQuery Validate required() 方法,该方法随后在随附的 <span> 元素中显示该消息。

    HTML

    <form action="/Movies/Create" method="post">
        <div class="form-horizontal">
            <h4>Movie</h4>
            <div class="text-danger"></div>
            <div class="form-group">
                <label class="col-md-2 control-label" for="ReleaseDate">ReleaseDate</label>
                <div class="col-md-10">
                    <input class="form-control" type="datetime"
                    data-val="true" data-val-required="The ReleaseDate field is required."
                    id="ReleaseDate" name="ReleaseDate" value="" />
                    <span class="text-danger field-validation-valid"
                    data-valmsg-for="ReleaseDate" data-valmsg-replace="true"></span>
                </div>
            </div>
        </div>
    </form>
    

    客户端验证将阻止提交,直到表单变为有效为止。 “提交”按钮运行 JavaScript:要么提交表单要么显示错误消息。

    MVC 基于属性的 .NET 数据类型确定类型特性值(有可能使用 [DataType] 特性进行重写)。 [DataType] 基本特性不执行真正的服务器端验证。 浏览器选择自己的错误消息,并根据需要显示这些错误,但 jQuery 非介入式验证包可以重写消息,并使它们与其他消息的显示保持一致。 当用户应用 [DataType] 子类(比如 [EmailAddress])时,最常发生这种情况。

    向动态表单添加验证

    由于 jQuery 非介入式验证会在第一次加载页面时将验证逻辑和参数传递到 jQuery Validate,因此,动态生成的表单不会自动展示验证。 你必须指示 jQuery 非介入式验证在创建动态表单后立即对其进行分析。 例如,下面的代码展示如何对通过 AJAX 添加的表单设置客户端验证。

    JavaScript

    $.get({
        url: "https://url/that/returns/a/form",
        dataType: "html",
        error: function(jqXHR, textStatus, errorThrown) {
            alert(textStatus + ": Couldn't add form. " + errorThrown);
        },
        success: function(newFormHTML) {
            var container = document.getElementById("form-container");
            container.insertAdjacentHTML("beforeend", newFormHTML);
            var forms = container.getElementsByTagName("form");
            var newForm = forms[forms.length - 1];
            $.validator.unobtrusive.parse(newForm);
        }
    })
    

    $.validator.unobtrusive.parse() 方法采用 jQuery 选择器作为它的一个参数。 此方法指示 jQuery 非介入式验证分析该选择器内表单的 data- 属性。 这些属性的值随后传递到 jQuery Validate 插件中,以便表单展示所需的客户端验证规则。

    向动态控件添加验证

    也可以在动态生成各个控件(比如 <input/><select/>)时,更新表单上的验证规则。 不能将用于这些元素的选择器直接传递到 parse() 方法,因为周围表单已进行分析并且不会更新。 应当先删除现有的验证数据,然后重新分析整个表单,如下所示:

    JavaScript

    $.get({
        url: "https://url/that/returns/a/control",
        dataType: "html",
        error: function(jqXHR, textStatus, errorThrown) {
            alert(textStatus + ": Couldn't add control. " + errorThrown);
        },
        success: function(newInputHTML) {
            var form = document.getElementById("my-form");
            form.insertAdjacentHTML("beforeend", newInputHTML);
            $(form).removeData("validator")    // Added by jQuery Validate
                   .removeData("unobtrusiveValidation");   // Added by jQuery Unobtrusive Validation
            $.validator.unobtrusive.parse(form);
        }
    })
    

    IClientModelValidator

    可为自定义属性创建客户端逻辑,创建 jQuery 验证的适配器的非介入式验证将在验证过程中,在客户端上自动为你执行此逻辑。 第一步是通过实现 IClientModelValidator 接口来控制要添加哪些 data- 属性,如下所示:

    C#

    public void AddValidation(ClientModelValidationContext context)
    {
        if (context == null)
        {
            throw new ArgumentNullException(nameof(context));
        }
    
        MergeAttribute(context.Attributes, "data-val", "true");
        MergeAttribute(context.Attributes, "data-val-classicmovie", GetErrorMessage());
    
        var year = _year.ToString(CultureInfo.InvariantCulture);
        MergeAttribute(context.Attributes, "data-val-classicmovie-year", year);
    }
    

    实现此接口的属性可以将 HTML 属性添加到生成的字段。 检查 ReleaseDate 元素的输出时,将显示与上一示例类似的 HTML,唯一不同的是,此示例包含一个已在 IClientModelValidatorAddValidation 方法中定义的 data-val-classicmovie 属性。

    HTML

    <input class="form-control" type="datetime"
        data-val="true"
        data-val-classicmovie="Classic movies must have a release year earlier than 1960."
        data-val-classicmovie-year="1960"
        data-val-required="The ReleaseDate field is required."
        id="ReleaseDate" name="ReleaseDate" value="" />
    

    非介入式验证使用 data- 属性中的数据来显示错误消息。 不过,除非将规则或消息添加到 jQuery 的 validator 对象,否则 jQuery 并不知道它们的存在。 如以下示例所示,将一个自定义 classicmovie 客户端验证方法添加到 validator 对象。 有关 unobtrusive.adapters.add 方法的说明,请参阅 ASP.NET MVC 中的非介入式客户端验证

    JavaScript

    $.validator.addMethod('classicmovie',
        function (value, element, params) {
            // Get element value. Classic genre has value '0'.
            var genre = $(params[0]).val(),
                year = params[1],
                date = new Date(value);
            if (genre && genre.length > 0 && genre[0] === '0') {
                // Since this is a classic movie, invalid if release date is after given year.
                return date.getFullYear() <= year;
            }
    
            return true;
        });
    
    $.validator.unobtrusive.adapters.add('classicmovie',
        ['year'],
        function (options) {
            var element = $(options.form).find('select#Genre')[0];
            options.rules['classicmovie'] = [element, parseInt(options.params['year'])];
            options.messages['classicmovie'] = options.message;
        });
    

    classicmovie 方法使用前面的代码对电影发行日期执行客户端验证。 如果该方法返回 false,则显示错误消息。

    远程验证

    远程验证是一项非常不错的功能,可在需要根据服务器上的数据验证客户端上的数据时使用。 例如,应用可能需要验证某个电子邮件或用户名是否已被使用,并且它必须为此查询大量数据。 为验证一个或几个字段而下载大量数据会占用过多资源。 它还有可能暴露敏感信息。 一种替代方法是发出往返请求来验证字段。

    可以分两步实现远程验证。 首先,必须使用 [Remote] 属性为模型添加批注。 [Remote] 属性采用多个重载,可用于将客户端 JavaScript 定向到要调用的相应代码。 下面的示例指向 Users 控制器的 VerifyEmail 操作方法。

    C#

    [Remote(action: "VerifyEmail", controller: "Users")]
    public string Email { get; set; }
    

    第二步是按照 [Remote] 属性中的定义,将验证代码放入相应的操作方法。 根据 jQuery Validate remote 方法文档,服务器响应必须是符合以下条件的 JSON 字符串:

    • 对于有效元素,为 "true"
    • 对于无效元素,为 "false"undefinednull,使用默认错误消息。

    如果服务器响应是一个字符串(例如,"That name is already taken, try peter123 instead"),则该字符串显示为一条自定义错误消息来替代默认字符串。

    VerifyEmail 方法的定义遵循这些规则,如下所示。 如果电子邮件已被占用,它会返回验证错误消息;如果电子邮件可用,则返回 true,并将结果包装在 JsonResult 对象中。 然后,客户端可以使用返回的值,继续进行下一步操作或根据需要显示错误。

    C#

    [AcceptVerbs("Get", "Post")]
    public IActionResult VerifyEmail(string email)
    {
        if (!_userRepository.VerifyEmail(email))
        {
            return Json($"Email {email} is already in use.");
        }
    
        return Json(true);
    }
    

    现在,当用户输入电子邮件时,视图中的 JavaScript 会发出远程调用,以了解该电子邮件是否已被占用,如果是,则显示错误消息。 如果不是,用户就可以像往常一样提交表单。

    [Remote] 特性的 AdditionalFields 属性可用于根据服务器上的数据验证字段组合。 例如,如果上面的 User 模型具有两个附加属性,名为 FirstNameLastName,你可能想要验证该名称对尚未被现有用户占用。 按以下代码所示定义新属性:

    C#

    [Remote(action: "VerifyName", controller: "Users", AdditionalFields = nameof(LastName))]
    public string FirstName { get; set; }
    [Remote(action: "VerifyName", controller: "Users", AdditionalFields = nameof(FirstName))]
    public string LastName { get; set; }
    

    AdditionalFields 可能已显式设置为字符串 "FirstName""LastName",但使用 nameof 这样的操作符可简化稍后的重构过程。 然后,用于执行验证的操作方法必须采用两个参数,一个用于 FirstName 的值,一个用于 LastName 的值。

    C#

    [AcceptVerbs("Get", "Post")]
    public IActionResult VerifyName(string firstName, string lastName)
    {
        if (!_userRepository.VerifyName(firstName, lastName))
        {
            return Json(data: $"A user named {firstName} {lastName} already exists.");
        }
    
        return Json(data: true);
    }
    

    现在,当用户输入名和姓时,JavaScript 会:

    • 发出远程调用,以了解该名称对是否已被占用。
    • 如果被占用,则显示一条错误消息。
    • 如果未被占用,则用户可以提交表单。

    如果需要使用 [Remote] 特性验证两个或更多附加字段,可将其以逗号分隔的列表形式列出。 例如,若要向模型中添加 MiddleName 属性,可按以下代码所示设置 [Remote] 特性:

    C#

    [Remote(action: "VerifyName", controller: "Users", AdditionalFields = nameof(FirstName) + "," + nameof(LastName))]
    public string MiddleName { get; set; }
    

    AdditionalFields 与所有属性参数一样,必须是常量表达式。 因此,不能使用内插字符串或调用 string.Join() 来初始化 AdditionalFields。 对于添加到 [Remote] 特性的每个附加字段,都必须向相应的控制器操作方法另外添加一个参数。

    展开全文
  • 人脸验证(三)--FaceNet

    千次阅读 2017-11-12 11:31:45
    转自:blog.csdn.net/stdcoutzyx/article/details/46687471 ...特贡献出FaceNet再次刷新LFW上人脸验证的效果记录。 本文是阅读FaceNet论文的笔记,所有配图均来自于论文。 转载请注明:http://blog.csdn.net...

    转自:blog.csdn.net/stdcoutzyx/article/details/46687471

    引入

    随着深度学习的出现,CV领域突破很多,甚至掀起了一股CV界的创业浪潮,当次风口浪尖之时,Google岂能缺席。特贡献出FaceNet再次刷新LFW上人脸验证的效果记录。

    本文是阅读FaceNet论文的笔记,所有配图均来自于论文。
    转载请注明:http://blog.csdn.net/stdcoutzyx/article/details/46687471

    FaceNet

    与其他的深度学习方法在人脸上的应用不同,FaceNet并没有用传统的softmax的方式去进行分类学习,然后抽取其中某一层作为特征,而是直接进行端对端学习一个从图像到欧式空间的编码方法,然后基于这个编码再做人脸识别、人脸验证和人脸聚类等。

    FaceNet算法有如下要点:

    • 去掉了最后的softmax,而是用元组计算距离的方式来进行模型的训练。使用这种方式学到的图像表示非常紧致,使用128位足矣。
    • 元组的选择非常重要,选的好可以很快的收敛。

    先看具体细节。

    网络架构

    大体架构与普通的卷积神经网络十分相似:

    img

    如图所示:Deep Architecture就是卷积神经网络去掉sofmax后的结构,经过L2的归一化,然后得到特征表示,基于这个特征表示计算三元组损失。

    目标函数

    在看FaceNet的目标函数前,其实要想一想DeepID2和DeepID2+算法,他们都添加了验证信号,但是是以加权的形式和softmax目标函数混合在一起。Google做的更多,直接替换了softmax。

    img

    所谓的三元组就是三个样例,如(anchor, pos, neg),其中,x和p是同一类,x和n是不同类。那么学习的过程就是学到一种表示,对于尽可能多的三元组,使得anchor和pos的距离,小于anchor和neg的距离。即:

    img

    所以,变换一下,得到目标函数:

    img

    目标函数的含义就是对于不满足条件的三元组,进行优化;对于满足条件的三元组,就pass先不管。

    三元组的选择

    很少的数据就可以产生很多的三元组,如果三元组选的不得法,那么模型要很久很久才能收敛。因而,三元组的选择特别重要。

    当然最暴力的方法就是对于每个样本,从所有样本中找出离他最近的反例和离它最远的正例,然后进行优化。这种方法有两个弊端:

    • 耗时,基本上选三元组要比训练还要耗时了,且等着吧。
    • 容易受不好的数据的主导,导致得到的模型会很差。

    所以,为了解决上述问题,论文中提出了两种策略。

    • 每N步线下在数据的子集上生成一些triplet
    • 在线生成triplet,在每一个mini-batch中选择hard pos/neg 样例。

    为了使mini-batch中生成的triplet合理,生成mini-batch的时候,保证每个mini-batch中每个人平均有40张图片。然后随机加一些反例进去。在生成triplet的时候,找出所有的anchor-pos对,然后对每个anchor-pos对找出其hard neg样本。这里,并不是严格的去找hard的anchor-pos对,找出所有的anchor-pos对训练的收敛速度也很快。

    除了上述策略外,还可能会选择一些semi-hard的样例,所谓的semi-hard即不考虑alpha因素,即:

    img

    网络模型

    论文使用了两种卷积模型:

    • 第一种是Zeiler&Fergus架构,22层,140M参数,1.6billion FLOPS(FLOPS是什么?)。称之为NN1。
    • 第二种是GoogleNet式的Inception模型。模型参数是第一个的20分之一,FLOPS是第一个的五分之一。
    • 基于Inception模型,减小模型大小,形成两个小模型。
      • NNS1:26M参数,220M FLOPS。
      • NNS2:4.3M参数,20M FLOPS。
    • NN3与NN4和NN2结构一样,但输入变小了。
      • NN2原始输入:224×224
      • NN3输入:160×160
      • NN4输入:96×96

    其中,NNS模型可以在手机上运行。

    其实网络模型的细节不用管,将其当做黑盒子就可以了。

    数据和评测

    在人脸识别领域,我一直认为数据的重要性很大,甚至强于模型,google的数据量自然不能小觑。其训练数据有100M-200M张图像,分布在8M个人上。

    当然,google训练的模型在LFW和youtube Faces DB上也进行了评测。

    下面说明了多种变量对最终效果的影响

    网络结构的不同

    img

    img

    图像质量的不同

    img

    最终生成向量表示的大小的不同

    img

    训练数据大小的不同

    img

    对齐与否

    在LFW上,使用了两种模式:

    • 直接取LFW图片的中间部分进行训练,效果98.87左右。
    • 使用额外的人脸对齐工具,效果99.63左右,超过deepid。

    总结

    • 三元组的目标函数并不是这篇论文首创,我在之前的一些Hash索引的论文中也见过相似的应用。可见,并不是所有的学习特征的模型都必须用softmax。用其他的效果也会好。
    • 三元组比softmax的优势在于
      • softmax不直接,(三元组直接优化距离),因而性能也不好。
      • softmax产生的特征表示向量都很大,一般超过1000维。
    • FaceNet并没有像DeepFace和DeepID那样需要对齐。
    • FaceNet得到最终表示后不用像DeepID那样需要再训练模型进行分类,直接计算距离就好了,简单而有效。
    • 论文并未探讨二元对的有效性,直接使用的三元对。

    参考文献

    [1]. Schroff F, Kalenichenko D, Philbin J. Facenet: A unified embedding for face recognition and clustering[J]. arXiv preprint arXiv:1503.03832, 2015.

    展开全文
  • layui原生表单验证

    千次阅读 2017-07-18 10:34:19
    在网上看到很多validform和layer配合的验证方式,但是觉得写的不好,不清不楚的,于是研究了一下layui原生的... lay-filter=“go” 个属性 value: required(必填项) phone(手机号) email(邮箱) url(网址

    在网上看到很多validform和layer配合的验证方式,但是觉得写的不好,不清不楚的,于是研究了一下layui原生的验证


    1.在需要验证的item上加 lay-verify=“value” ,在提交按钮上加 lay-submit  lay-filter=“go”  两个属性

    value:

    required(必填项)
    phone(手机号)
    email(邮箱)
    url(网址)
    number(数字)
    date(日期)
    identity(身份证)
    自定义值(就是自定义验证规则)


    PS :layui要使用form  得用use...这样的东西,我就不做说明了

    layui.use('form', function(){
        var form = layui.form(); //只有执行了这一步,部分表单元素才会修饰成功
    
    
        2.说一下自定义验证
        在这里写自定义的验证规则,“username”和“pass”是自定义验证规则的名字,就跟前边的"required","phone"...一样,在这里定义好验证的名字和验证规则,
        使用的方法就跟"required","phone"...一样一样的
    
        form.verify({
            username: function(value, item){ //value:表单的值、item:表单的DOM对象
                if(!new RegExp("^[a-zA-Z0-9_\u4e00-\u9fa5\\s·]+$").test(value)){
                    return '用户名不能有特殊字符';
                }
                if(/(^\_)|(\__)|(\_+$)/.test(value)){
                    return '用户名首尾不能出现下划线\'_\'';
                }
                if(/^\d+\d+\d$/.test(value)){
                    return '用户名不能全为数字';
                }
            }
    
            //我们既支持上述函数式的方式,也支持下述数组的形式
            //数组的两个值分别代表:[正则匹配、匹配不符时的提示文字]
            ,pass: [
                /^[\S]{6,12}$/,'密码必须6到12位,且不能出现空格'
            ]
        });
    
    
    3.验证通过了就触发提交
    ‘submit(go)’这个其实就是绑定“提交按钮”,还记得第一步让你加的两个属性吧 lay-submit  lay-filter=“go” ,懂了吧!go是可以随便写的
    这里指的一提的是data.field这个东西,它会获得 全部表单字段,名值对形式:{name: value},
    这样我们在发送ajax的时候就不必自己去收集表单的字段值了
    form.on('submit(go)', function(data){
        //console.log(data.elem);//被执行事件的元素DOM对象,一般为button对象
        //console.log(data.form);//被执行提交的form对象,一般在存在form标签时才会返回
        //console.log(data.field); //当前容器的全部表单字段,名值对形式:{name: value}
       
        //发送ajax
    
        return false; //阻止表单跳转。如果需要表单跳转,去掉这段即可。
    });
    });





    展开全文
  • 本系列其他文章见:《响应Spring的道法术器》。 前情提要:响应流 | lambda与函数 | Reactor快速上手 1.3.3 Spring WebFlux Spring WebFlux是随Spring 5推出的响应Web框架。 1)服务端技术栈 ...

    本系列其他文章见:《响应式Spring的道法术器》
    前情提要:响应式流 | lambda与函数式 | Reactor快速上手

    1.3.3 Spring WebFlux

    Spring WebFlux是随Spring 5推出的响应式Web框架。

    watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2dldF9zZXQ=,size_16,color_FFFFFF,t_70

    1)服务端技术栈

    Spring提供了完整的支持响应式的服务端技术栈。

    如上图所示,左侧为基于spring-webmvc的技术栈,右侧为基于spring-webflux的技术栈,

    • Spring WebFlux是基于响应式流的,因此可以用来建立异步的、非阻塞的、事件驱动的服务。它采用Reactor作为首选的响应式流的实现库,不过也提供了对RxJava的支持。
    • 由于响应式编程的特性,Spring WebFlux和Reactor底层需要支持异步的运行环境,比如Netty和Undertow;也可以运行在支持异步I/O的Servlet 3.1的容器之上,比如Tomcat(8.0.23及以上)和Jetty(9.0.4及以上)。
    • 从图的纵向上看,spring-webflux上层支持两种开发模式:
      • 类似于Spring WebMVC的基于注解(@Controller@RequestMapping)的开发模式;
      • Java 8 lambda 风格的函数式开发模式。
    • Spring WebFlux也支持响应式的Websocket服务端开发。

    由此看来,Spring WebFlux与Vert.x有一些相通之处,都是建立在非阻塞的异步I/O和事件驱动的基础之上的。

    2)响应式Http客户端

    此外,Spring WebFlux也提供了一个响应式的Http客户端API WebClient。它可以用函数式的方式异步非阻塞地发起Http请求并处理响应。其底层也是由Netty提供的异步支持。

    我们可以把WebClient看做是响应式的RestTemplate,与后者相比,前者:

    • 是非阻塞的,可以基于少量的线程处理更高的并发;
    • 可以使用Java 8 lambda表达式;
    • 支持异步的同时也可以支持同步的使用方式;
    • 可以通过数据流的方式与服务端进行双向通信。

    当然,与服务端对应的,Spring WebFlux也提供了响应式的Websocket客户端API。

    简单介绍这些,让我们来Coding吧(本文源码)~

    本节,我们仍然是本着“Hello,world!”的精神来上手熟悉WebFlux,因此暂时不会像手册一样面面俱到地谈到WebFlux的各个细节,我们通过以下几个例子来了解它:

    1. 先介绍一下使用Spring WebMVC风格的基于注解的方式如何编写响应式的Web服务,这几乎没有学习成本,非常赞。虽然这种方式在开发上与Spring WebMVC变化不大,但是框架底层已经是完全的响应式技术栈了;
    2. 再进一步介绍函数式的开发模式;
    3. 简单几行代码实现服务端推送(Server Send Event,SSE);
    4. 然后我们再加入响应式数据库的支持(使用Reactive Spring Data for MongoDB);
    5. 使用WebClient与前几步做好的服务端进行通信;
    6. 最后我们看一下如何通过“流”的方式在Http上进行通信。

    Spring Boot 2是基于Spring 5的,其中一个比较大的更新就在于支持包括spring-webflux和响应式的spring-data在内的响应式模块。Spring Boot 2即将发布正式版,不过目前的版本从功能上已经完备,下边的例子我们就用Spring Boot 2在进行搭建。

    1.3.3.1 基于WebMVC注解的方式

    我们首先用Spring WebMVC开发一个只有Controller层的简单的Web服务,然后仅仅做一点点调整就可切换为基于Spring WebFlux的具有同样功能的Web服务。

    我们使用Spring Boot 2搭建项目框架。

    以下截图来自IntelliJ IDEA,不过其他IDE也都是类似的。

    1)基于Spring Initializr创建项目

    本节的例子很简单,不涉及Service层和Dao层,因此只选择spring-webmvc即可,也就是“Web”的starter。

    watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2dldF9zZXQ=,size_16,color_FFFFFF,t_70

    也可以使用网页版的https://start.spring.io来创建项目:

    watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2dldF9zZXQ=,size_16,color_FFFFFF,t_70

    创建后的项目POM中,包含下边的依赖,即表示基于Spring WebMVC:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    

    2)创建Controller和Endpoint

    创建Controller类HelloController,仅提供一个Endpoint:/hello

        @RestController
        public class HelloController {
        
            @GetMapping("/hello")
            public String hello() {
                return "Welcome to reactive world ~";
            }
        }
    

    3)启动应用

    OK了,一个简单的基于Spring WebMVC的Web服务。我们新增了HelloController.java,修改了application.properties

    watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2dldF9zZXQ=,size_16,color_FFFFFF,t_70

    使用IDE启动应用,或使用maven命令:

    mvn spring-boot:run
    

    通过打印的log可以看到,服务运行于Tomcat的8080端口:

    测试Endpoint。在浏览器中访问http://localhost:8080/hello,或运行命令:

    curl http://localhost:8080/hello
    

    返回Welcome to reactive world ~

    基于Spring WebFlux的项目与上边的步骤一致,仅有两点不同。我们这次偷个懒,就不从新建项目了,修改一下上边的项目:

    4)依赖“Reactive Web”的starter而不是“Web”

    修改项目POM,调整依赖使其基于Spring WebFlux:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>    <!--【改】增加“flux”四个字符-->
        </dependency>
    

    5)Controller中处理请求的返回类型采用响应式类型

        @RestController
        public class HelloController {
        
            @GetMapping("/hello")
            public Mono<String> hello() {   // 【改】返回类型为Mono<String>
                return Mono.just("Welcome to reactive world ~");     // 【改】使用Mono.just生成响应式数据
            }
        }
    

    6)启动应用

    仅需要上边两步就改完了,是不是很简单,同样的方法启动应用。启动后发现应用运行于Netty上:

    访问http://localhost:8080/hello,结果与Spring WebMVC的相同。

    7)总结

    从上边这个非常非常简单的例子中可以看出,Spring真是用心良苦,WebFlux提供了与之前WebMVC相同的一套注解来定义请求的处理,使得Spring使用者迁移到响应式开发方式的过程变得异常轻松。

    虽然我们只修改了少量的代码,但是其实这个简单的项目已经脱胎换骨了。整个技术栈从命令式的、同步阻塞的【spring-webmvc + servlet + Tomcat】变成了响应式的、异步非阻塞的【spring-webflux + Reactor + Netty】。

    Netty是一套异步的、事件驱动的网络应用程序框架和工具,能够开发高性能、高可靠性的网络服务器和客户端程序,因此与同样是异步的、事件驱动的响应式编程范式一拍即合。

    下边的内容了解即可,就不实战了。
    在Java 7推出异步I/O库,以及Servlet3.1增加了对异步I/O的支持之后,Tomcat等Servlet容器也随后开始支持异步I/O,然后Spring WebMVC也增加了对Reactor库的支持,所以上边第4)步如果不是将spring-boot-starter-web替换为spring-boot-starter-WebFlux,而是增加reactor-core的依赖的话,仍然可以用注解的方式开发基于Tomcat的响应式应用。

    1.3.3.2 WebFlux的函数式开发模式

    既然是响应式编程了,有些朋友可能会想统一用函数式的编程风格,WebFlux满足你。WebFlux提供了一套函数式接口,可以用来实现类似MVC的效果。我们先接触两个常用的。

    再回头瞧一眼上边例子中我们用Controller定义定义对Request的处理逻辑的方式,主要有两个点:

    1. 方法定义处理逻辑;
    2. 然后用@RequestMapping注解定义好这个方法对什么样url进行响应。

    在WebFlux的函数式开发模式中,我们用HandlerFunctionRouterFunction来实现上边这两点。

    • HandlerFunction相当于Controller中的具体处理方法,输入为请求,输出为装在Mono中的响应:
        Mono<T extends ServerResponse> handle(ServerRequest request);
    
    • RouterFunction,顾名思义,路由,相当于@RequestMapping,用来判断什么样的url映射到那个具体的HandlerFunction,输入为请求,输出为装在Mono里边的Handlerfunction
        Mono<HandlerFunction<T>> route(ServerRequest request);
    

    我们看到,在WebFlux中,请求和响应不再是WebMVC中的ServletRequestServletResponse,而是ServerRequestServerResponse。后者是在响应式编程中使用的接口,它们提供了对非阻塞和回压特性的支持,以及Http消息体与响应式类型Mono和Flux的转换方法。

    下面我们用函数式的方式开发两个Endpoint:

    1. /time返回当前的时间;
    2. /date返回当前的日期。

    对于这两个需求,HandlerFunction很容易写:

        // 返回包含时间字符串的ServerResponse
        HandlerFunction<ServerResponse> timeFunction = 
            request -> ServerResponse.ok().contentType(MediaType.TEXT_PLAIN).body(
                Mono.just("Now is " + new SimpleDateFormat("HH:mm:ss").format(new Date())), String.class);
        
        // 返回包含日期字符串的ServerResponse
        HandlerFunction<ServerResponse> dateFunction = 
            request -> ServerResponse.ok().contentType(MediaType.TEXT_PLAIN).body(
                Mono.just("Today is " + new SimpleDateFormat("yyyy-MM-dd").format(new Date())), String.class);
    

    那么RouterFunction为:

        RouterFunction<ServerResponse> router = 
            RouterFunctions.route(GET("/time"), timeFunction)
                .andRoute(GET("/date"), dateFunction);
    

    按照常见的套路,RouterFunctions是工具类。

    不过这么写在业务逻辑复杂的时候不太好组织,我们通常采用跟MVC类似的代码组织方式,将同类业务的HandlerFunction放在一个类中,然后在Java Config中将RouterFunction配置为Spring容器的Bean。我们继续在第一个例子的代码上开发:

    1)创建统一存放处理时间的Handler类

    创建TimeHandler.java

        import static org.springframework.web.reactive.function.server.ServerResponse.ok;
    
        @Component
        public class TimeHandler {
            public Mono<ServerResponse> getTime(ServerRequest serverRequest) {
                return ok().contentType(MediaType.TEXT_PLAIN).body(Mono.just("Now is " + new SimpleDateFormat("HH:mm:ss").format(new Date())), String.class);
            }
            public Mono<ServerResponse> getDate(ServerRequest serverRequest) {
                return ok().contentType(MediaType.TEXT_PLAIN).body(Mono.just("Today is " + new SimpleDateFormat("yyyy-MM-dd").format(new Date())), String.class);
            }
        }
    

    由于出现次数通常比较多,这里静态引入ServerResponse.ok()方法。

    2)在Spring容器配置RouterFunction

    我们采用Spring现在比较推荐的Java Config的配置Bean的方式,创建用于存放Router的配置类RouterConfig.java

        import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
        import static org.springframework.web.reactive.function.server.RouterFunctions.route;
        
        @Configuration
        public class RouterConfig {
            @Autowired
            private TimeHandler timeHandler;
        
            @Bean
            public RouterFunction<ServerResponse> timerRouter() {
                return route(GET("/time"), req -> timeHandler.getTime(req))
                        .andRoute(GET("/date"), timeHandler::getDate);  // 这种方式相对于上一行更加简洁
            }
        }
    

    3)重启服务试一试

    重启服务测试一下吧:

    $ curl http://localhost:8080/date
    Today is 2018-02-26
    
    $ curl http://localhost:8080/time
    Now is 21:12:53
    

    1.3.3.3 服务器推送

    我们可能会遇到一些需要网页与服务器端保持连接(起码看上去是保持连接)的需求,比如类似微信网页版的聊天类应用,比如需要频繁更新页面数据的监控系统页面或股票看盘页面。我们通常采用如下几种技术:

    • 短轮询:利用ajax定期向服务器请求,无论数据是否更新立马返回数据,高并发情况下可能会对服务器和带宽造成压力;
    • 长轮询:利用comet不断向服务器发起请求,服务器将请求暂时挂起,直到有新的数据的时候才返回,相对短轮询减少了请求次数;
    • SSE:服务端推送(Server Send Event),在客户端发起一次请求后会保持该连接,服务器端基于该连接持续向客户端发送数据,从HTML5开始加入。
    • Websocket:这是也是一种保持连接的技术,并且是双向的,从HTML5开始加入,并非完全基于HTTP,适合于频繁和较大流量的双向通讯场景。

    既然响应式编程是一种基于数据流的编程范式,自然在服务器推送方面得心应手,我们基于函数式方式再增加一个Endpoint /times,可以每秒推送一次时间。

    1)增加Handler方法

    TimeHandler.java

        public Mono<ServerResponse> sendTimePerSec(ServerRequest serverRequest) {
            return ok().contentType(MediaType.TEXT_EVENT_STREAM).body(  // 1
                    Flux.interval(Duration.ofSeconds(1)).   // 2
                            map(l -> new SimpleDateFormat("HH:mm:ss").format(new Date())), 
                    String.class);
        }
    
    1. MediaType.TEXT_EVENT_STREAM表示Content-Typetext/event-stream,即SSE;
    2. 利用interval生成每秒一个数据的流。

    2)配置router

    RouterConfig.java

            @Bean
            public RouterFunction<ServerResponse> timerRouter() {
                return route(GET("/time"), timeHandler::getTime)
                        .andRoute(GET("/date"), timeHandler::getDate)
                        .andRoute(GET("/times"), timeHandler::sendTimePerSec);  // 增加这一行
            }
    

    3)重启服务试一下

    重启服务后,测试一下:

    curl http://localhost:8080/times
    data:21:32:22
    data:21:32:23
    data:21:32:24
    data:21:32:25
    data:21:32:26
    <Ctrl+C>
    

    就酱,访问这个url会收到持续不断的报时数据(时间数据是在data中的)。

    那么用注解的方式如何进行服务端推送呢,这个演示就融到下一个例子中吧~

    1.3.3.3 响应式Spring Data

    开发基于响应式流的应用,就像是在搭建数据流流动的管道,从而异步的数据能够顺畅流过每个环节。前边的例子主要聚焦于应用层,然而绝大多数系统免不了要与数据库进行交互,所以我们也需要响应式的持久层API和支持异步的数据库驱动。就像从自来水厂到家里水龙头这个管道中,如果任何一个环节发生了阻塞,那就可能造成整体吞吐量的下降。

    各个数据库都开始陆续推出异步驱动,目前Spring Data支持的可以进行响应式数据访问的数据库有MongoDB、Redis、Apache Cassandra和CouchDB。今天我们用MongoDB来写一个响应式demo。

    我们这个例子很简单,就是关于User的增删改查,以及基于注解的服务端推送。

    1)编写User

    既然是举例,我们随便定义几个属性吧~

        public class User {
            private String id;
            private String username;
            private String phone;
            private String email;
            private String name;
            private Date birthday;
        }
    

    然后为了方便开发,我们引入lombok库,它能够通过注解的方式为我们添加必要的Getter/Setter/hashCode()/equals()/toString()/构造方法等,添加依赖(版本可自行到http://search.maven.org搜索最新):

    	<dependency>
    		<groupId>org.projectlombok</groupId>
    		<artifactId>lombok</artifactId>
    		<version>1.16.20</version>
    	</dependency>
    

    然后为User添加注解:

        @Data   // 生成无参构造方法/getter/setter/hashCode/equals/toString
        @AllArgsConstructor // 生成所有参数构造方法
        @NoArgsConstructor  // @AllArgsConstructor会导致@Data不生成无参构造方法,需要手动添加@NoArgsConstructor,如果没有无参构造方法,可能会导致比如com.fasterxml.jackson在序列化处理时报错
        public class User {
            ...
    

    我们可以利用IDE看一下生成的方法(如下图黄框所示):

    watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2dldF9zZXQ=,size_16,color_FFFFFF,t_70

    可能需要先在IDE中进行少量配置以便支持lombok的注解,比如IntelliJ IDEA:

    1. 安装“lombok plugin”:
      watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2dldF9zZXQ=,size_16,color_FFFFFF,t_70
    1. 开启对注解编译的支持:
      watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2dldF9zZXQ=,size_16,color_FFFFFF,t_70

    lombok对于Java开发者来说绝对算是个福音了,希望使用Kotlin的朋友不要笑话我们土哦~

    2)增加Spring Data的依赖

    在POM中增加Spring Data Reactive Mongo的依赖:

    	<dependency>
    		<groupId>org.springframework.boot</groupId>
    		<artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
    	</dependency>
    

    MongoDB是文档型的NoSQL数据库,因此,我们使用@Document注解User类:

        @Data
        @AllArgsConstructor
        @Document
        public class User {
            @Id
            private String id;      // 注解属性id为ID
            @Indexed(unique = true) // 注解属性username为索引,并且不能重复
            private String username;
            private String name;
            private String phone;
            private Date birthday;
        }
    

    OK,这样我们的模型就准备好了。MongoDB会自动创建collection,默认为类名首字母小写,也就是user

    3)配置数据源

    Spring Boot为我们搞定了几乎所有的配置,太赞了,下边是MongoDB的默认配置:

    # MONGODB (MongoProperties)
    spring.data.mongodb.authentication-database= # Authentication database name.
    spring.data.mongodb.database=test # Database name.
    spring.data.mongodb.field-naming-strategy= # Fully qualified name of the FieldNamingStrategy to use.
    spring.data.mongodb.grid-fs-database= # GridFS database name.
    spring.data.mongodb.host=localhost # Mongo server host. Cannot be set with uri.
    spring.data.mongodb.password= # Login password of the mongo server. Cannot be set with uri.
    spring.data.mongodb.port=27017 # Mongo server port. Cannot be set with uri.
    spring.data.mongodb.repositories.enabled=true # Enable Mongo repositories.
    spring.data.mongodb.uri=mongodb://localhost/test # Mongo database URI. Cannot be set with host, port and credentials.
    spring.data.mongodb.username= # Login user of the mongo server. Cannot be set with uri.
    

    请根据需要添加自定义的配置,比如我的MongoDB是跑在IP为192.168.0.101的虚拟机的Docker中的,就可在application.properties中增加一条:

    spring.data.mongodb.host=192.168.0.101
    

    4)增加DAO层repository

    与非响应式Spring Data的CrudReposity对应的,响应式的Spring Data也提供了相应的Repository库:ReactiveCrudReposity,当然,我们也可以使用它的子接口ReactiveMongoRepository

    我们增加UserRepository

        public interface UserRepository extends ReactiveCrudRepository<User, String> {  // 1
            Mono<User> findByUsername(String username);     // 2
            Mono<Long> deleteByUsername(String username);
        }
    
    1. 同样的,ReactiveCrudRepository的泛型分别是UserID的类型;
    2. ReactiveCrudRepository已经提供了基本的增删改查的方法,根据业务需要,我们增加四个方法(在此膜拜一下Spring团队的牛人们,使得我们仅需按照规则定义接口方法名即可完成DAO层逻辑的开发,牛~)

    5)Service层

    由于业务逻辑几乎为零,只是简单调用了DAO层,直接贴代码:

        @Service
        public class UserService {
            @Autowired
            private UserRepository userRepository;
        
            /**
             * 保存或更新。
             * 如果传入的user没有id属性,由于username是unique的,在重复的情况下有可能报错,
             * 这时找到以保存的user记录用传入的user更新它。
             */
            public Mono<User> save(User user) {
                return userRepository.save(user)
                        .onErrorResume(e ->     // 1
                                userRepository.findByUsername(user.getUsername())   // 2
                                        .flatMap(originalUser -> {      // 4
                                            user.setId(originalUser.getId());
                                            return userRepository.save(user);   // 3
                                        }));
            }
        
            public Mono<Long> deleteByUsername(String username) {
                return userRepository.deleteByUsername(username);
            }
        
            public Mono<User> findByUsername(String username) {
                return userRepository.findByUsername(username);
            }
            
            public Flux<User> findAll() {
                return userRepository.findAll();
            }
        }
    
    1. onErrorResume进行错误处理;
    2. 找到username重复的记录;
    3. 拿到ID从而进行更新而不是创建;
    4. 由于函数式为User -> Publisher,所以用flatMap

    6)Controller层

    直接贴代码:

        @RestController
        @RequestMapping("/user")
        public class UserController {
            @Autowired
            private UserService userService;
        
            @PostMapping("")
            public Mono<User> save(User user) {
                return this.userService.save(user);
            }
        
            @DeleteMapping("/{username}")
            public Mono<Long> deleteByUsername(@PathVariable String username) {
                return this.userService.deleteByUsername(username);
            }
        
            @GetMapping("/{username}")
            public Mono<User> findByUsername(@PathVariable String username) {
                return this.userService.findByUsername(username);
            }
        
            @GetMapping("")
            public Flux<User> findAll() {
                return this.userService.findAll();
            }
        }
    

    7)启动应用测试一下

    由于涉及到POST和DELETE方法的请求,建议用支持RESTful的client来测试,比如“Restlet client”:

    如图,增加操作是成功的,只要username不变,再次发送请求会更新该记录。

    图中birthday的时间差8小时,不去管它。

    用同样的方法增加一个李四,之后我们再来测试一下查询。

    1. 根据用户名查询(METHOD:GET URL:http://localhost:8080/user/zhangsan),下边输出是格式化的JSON:

      {
      “id”: “5a9504a167646d057051e229”,
      “username”: “zhangsan”,
      “name”: “张三”,
      “phone”: “18610861861”,
      “birthday”: “1989-12-31T16:00:00.000+0000”
      }

    2. 查询全部(METHOD:GET URL:http://localhost:8080/user)

      [{“id”:“5a9504a167646d057051e229”,“username”:“zhangsan”,“name”:“张三”,“phone”:“18610861861”,“birthday”:“1989-12-31T16:00:00.000+0000”},{“id”:“5a9511db67646d3c782f2e7f”,“username”:“lisi”,“name”:“李四”,“phone”:“18610861862”,“birthday”:“1992-02-01T16:00:00.000+0000”}]

    测试一下删除(METHOD:DELETE URL:http://localhost:8080/user/zhangsan),返回值为1,再查询全部,发现张三已经被删除了,OK。

    8)stream+json

    看到这里细心的朋友可能会有点嘀咕,怎么看是不是异步的呢?毕竟查询全部的时候,结果都用中括号括起来了,这和原来返回List<User>的效果似乎没多大区别。假设一下查询100个数据,如果是异步的话,以我们对“异步响应式流”的印象似乎应该是一个一个至少是一批一批的到达客户端的嘛。我们加个延迟验证一下:

    	@GetMapping("")
    	public Flux<User> findAll() {
    	    return this.userService.findAll().delayElements(Duration.ofSeconds(1));
    	}
    

    每个元素都延迟1秒,现在我们在数据库里弄三条记录,然后请求查询全部的那个URL,发现并不是像/times一样一秒一个地出来,而是3秒之后一块儿出来的。果然如此,这一点都不响应式啊!

    /times类似,我们也加一个MediaType,不过由于这里返回的是JSON,因此不能使用TEXT_EVENT_STREAM,而是使用APPLICATION_STREAM_JSON,即application/stream+json格式。

    @GetMapping(value = "", produces = MediaType.APPLICATION_STREAM_JSON_VALUE)
    public Flux<User> findAll() {
        return this.userService.findAll().delayElements(Duration.ofSeconds(2));
    }
    
    1. produces后边的值应该是application/stream+json字符串,因此用APPLICATION_STREAM_JSON_VALUE

    重启服务再次请求,发现三个user是一秒一个的速度出来的,中括号也没有了,而是一个一个独立的JSON值构成的json stream:

    {"id":"5a9504a167646d057051e229","username":"zhangsan","name":"张三","phone":"18610861861","birthday":"1989-12-31T16:00:00.000+0000"}
    {"id":"5a9511db67646d3c782f2e7f","username":"lisi","name":"李四","phone":"18610861862","birthday":"1992-02-01T16:00:00.000+0000"}
    {"id":"5a955f08fa10b93ec48df37f","username":"wangwu","name":"王五","phone":"18610861865","birthday":"1995-05-04T16:00:00.000+0000"}
    

    9)总结

    如果有Spring Data开发经验的话,切换到Spring Data Reactive的难度并不高。跟Spring WebFlux类似:原来返回User的话,那现在就返回Mono<User>;原来返回List<User>的话,那现在就返回Flux<User>

    对于稍微复杂的业务逻辑或一些必要的异常处理,比如上边的save方法,请一定采用响应式的编程方式来定义,从而一切都是异步非阻塞的。如下图所示,从HttpServer(如Netty或Servlet3.1以上的Servlet容器)到ServerAdapter(Spring WebFlux框架提供的针对不同server的适配器),到我们编写的Controller和DAO,以及异步数据库驱动,构成了一个完整的异步非阻塞的管道,里边流动的就是响应式流。

    1.3.3.4 使用WebClient开发响应式Http客户端

    下面,我们用WebClient测试一下前边几个例子的成果。

    1) /hello,返回Mono

        @Test
        public void webClientTest1() throws InterruptedException {
            WebClient webClient = WebClient.create("http://localhost:8080");   // 1
            Mono<String> resp = webClient
                    .get().uri("/hello") // 2
                    .retrieve() // 3
                    .bodyToMono(String.class);  // 4
            resp.subscribe(System.out::println);    // 5
            TimeUnit.SECONDS.sleep(1);  // 6
        }
    
    1. 创建WebClient对象并指定baseUrl;
    2. HTTP GET;
    3. 异步地获取response信息;
    4. 将response body解析为字符串;
    5. 打印出来;
    6. 由于是异步的,我们将测试线程sleep 1秒确保拿到response,也可以像前边的例子一样用CountDownLatch

    运行效果如下:

    2) /user,返回Flux

    为了多演示一些不同的实现方式,下边的例子我们调整几个地方,但是效果跟上边是一样的:

        @Test
        public void webClientTest2() throws InterruptedException {
            WebClient webClient = WebClient.builder().baseUrl("http://localhost:8080").build(); // 1
            webClient
                    .get().uri("/user")
                    .accept(MediaType.APPLICATION_STREAM_JSON) // 2
                    .exchange() // 3
                    .flatMapMany(response -> response.bodyToFlux(User.class))   // 4
                    .doOnNext(System.out::println)  // 5
                    .blockLast();   // 6
        }
    
    1. 这次我们使用WebClientBuilder来构建WebClient对象;
    2. 配置请求Header:Content-Type: application/stream+json
    3. 获取response信息,返回值为ClientResponseretrive()可以看做是exchange()方法的“快捷版”;
    4. 使用flatMap来将ClientResponse映射为Flux;
    5. 只读地peek每个元素,然后打印出来,它并不是subscribe,所以不会触发流;
    6. 上个例子中sleep的方式有点low,blockLast方法,顾名思义,在收到最后一个元素前会阻塞,响应式业务场景中慎用。

    运行效果如下:
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dnZqusDX-1615630172707)(https://leanote.com/api/file/getImage?fileId=5a9570d6ab64415d3400b4f0)]

    3) /times,服务端推送

        @Test
        public void webClientTest3() throws InterruptedException {
            WebClient webClient = WebClient.create("http://localhost:8080");
            webClient
                    .get().uri("/times")
                    .accept(MediaType.TEXT_EVENT_STREAM)    // 1
                    .retrieve()
                    .bodyToFlux(String.class)
                    .log()  // 2
                    .take(10)   // 3
                    .blockLast();
        }
    
    1. 配置请求Header:Content-Type: text/event-stream,即SSE;
    2. 这次用log()代替doOnNext(System.out::println)来查看每个元素;
    3. 由于/times是一个无限流,这里取前10个,会导致流被取消

    运行效果如下:

    1.3.3.5 让数据在Http上双向无限流动起来

    许多朋友看到这个题目会想到Websocket,的确,Websocket确实可以实现全双工通信,但它的数据传输并非是完全基于HTTP协议的,关于Websocket我们后边再聊。

    下面我们实现一个这样两个Endpoint:

    • POST方法的/events,“源源不断”地收集数据,并存入数据库;
    • GET方法的/events,“源源不断”将数据库中的记录发出来。

    0)准备

    一、数据模型MyEvent

        @Data
        @AllArgsConstructor
        @NoArgsConstructor
        @Document(collection = "event") // 1
        public class MyEvent {
            @Id
            private Long id;    // 2
            private String message;
        }
    
    1. 指定collection名为event
    2. 这次我们使用表示时间的long型数据作为ID。

    二、DAO层:

        public interface MyEventRepository extends ReactiveMongoRepository<MyEvent, Long> { // 1
        }
    
    1. 下边用到了可以保存Flux的insert(Flux)方法,这个方法是在ReactiveMongoRepository中定义的。

    三、简单起见就不要Service层了,直接Controller:

        @RestController
        @RequestMapping("/events")
        public class MyEventController {
            @Autowired
            private MyEventRepository myEventRepository;
        
            @PostMapping(path = "")
            public Mono<Void> loadEvents(@RequestBody Flux<MyEvent> events) {   // 1
                // TODO
                return null;
            }
        
            @GetMapping(path = "", produces = MediaType.APPLICATION_STREAM_JSON_VALUE)
            public Flux<MyEvent> getEvents() {  // 2
                // TODO
                return null;
            }
        }
    
    1. POST方法的接收数据流的Endpoint,所以传入的参数是一个Flux,返回结果其实就看需要了,我们用一个Mono<Void>作为方法返回值,表示如果传输完的话只给一个“完成信号”就OK了;
    2. GET方法的无限发出数据流的Endpoint,所以返回结果是一个Flux<MyEvent>,不要忘了注解上produces = MediaType.APPLICATION_STREAM_JSON_VALUE

    准备到此为止,类如下。我们来完成上边的两个TODO吧。

    watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2dldF9zZXQ=,size_16,color_FFFFFF,t_70

    1)接收数据流的Endpoint

    在客户端,WebClient可以接收text/event-streamapplication/stream+json格式的数据流,也可以在请求的时候上传一个数据流到服务器;
    在服务端,WebFlux也支持接收一个数据流作为请求参数,从而实现一个接收数据流的Endpoint。

    我们先看服务端。Controller中的loadEvents方法:

        @PostMapping(path = "", consumes = MediaType.APPLICATION_STREAM_JSON_VALUE) // 1
        public Mono<Void> loadEvents(@RequestBody Flux<MyEvent> events) {
            return this.myEventRepository.insert(events).then();    // 2
        }
    
    1. 指定传入的数据是application/stream+json,与getEvents方法的区别在于这个方法是consume这个数据流;
    2. insert返回的是保存成功的记录的Flux,但我们不需要,使用then方法表示“忽略数据元素,只返回一个完成信号”。

    服务端写好后,启动之,再看一下客户端怎么写(还是放在src/test下):

        @Test
        public void webClientTest4() {
            Flux<MyEvent> eventFlux = Flux.interval(Duration.ofSeconds(1))
                    .map(l -> new MyEvent(System.currentTimeMillis(), "message-" + l)).take(5); // 1
            WebClient webClient = WebClient.create("http://localhost:8080");
            webClient
                    .post().uri("/events")
                    .contentType(MediaType.APPLICATION_STREAM_JSON) // 2
                    .body(eventFlux, MyEvent.class) // 3
                    .retrieve()
                    .bodyToMono(Void.class)
                    .block();
        }
    
    1. 声明速度为每秒一个MyEvent元素的数据流,不加take的话表示无限个元素的数据流;
    2. 声明请求体的数据格式为application/stream+json
    3. body方法设置请求体的数据。

    运行一下这个测试,根据控制台数据可以看到是一条一条将数据发到/events的,看一下MongoDB中的数据:

    watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2dldF9zZXQ=,size_16,color_FFFFFF,t_70

    2)发出无限流的Endpoint

    回想一下前边/user的例子,当数据库中所有的内容都查询出来之后,这个流就结束了,因为其后跟了一个“完成信号”,我们可以通过在UserServicefindAll()方法的流上增加log()操作符来观察更详细的日志:

    我们可以看到在三个onNext信号后是一个onComplete信号。

    这样的流是有限流,这个时候如果在数据库中再新增一个User的话,已经结束的请求也不会再有新的内容出现了。

    反观/times请求,它会无限地发出SSE,而不会有“完成信号”出现,这是无限流。

    我们希望的情况是无论是请求GET的/events之后,当所有数据都发完之后,不要结束,而是挂起等待新的数据。如果我们用上边的POST的/events传入新的数据到数据库后,新的数据会自动地流到客户端。

    这可以在DAO层配置实现:

        public interface MyEventRepository extends ReactiveMongoRepository<MyEvent, Long> {
            @Tailable   // 1
            Flux<MyEvent> findBy(); // 2
        }
    
    1. @Tailable注解的作用类似于linux的tail命令,被注解的方法将发送无限流,需要注解在返回值为Flux这样的多个元素的Publisher的方法上;
    2. findAll()是想要的方法,但是在ReactiveMongoRepository中我们够不着,所以使用findBy()代替。

    然后完成Controller中的方法:

        @GetMapping(path = "", produces = MediaType.APPLICATION_STREAM_JSON_VALUE)
        public Flux<MyEvent> getEvents() {
            return this.myEventRepository.findBy();
        }
    

    不过,这还不够,@Tailable仅支持有大小限制的(“capped”)collection,而自动创建的collection是不限制大小的,因此我们需要先手动创建。Spring Boot提供的CommandLineRunner可以帮助我们实现这一点。

    Spring Boot应用程序在启动后,会遍历CommandLineRunner接口的实例并运行它们的run方法。

        @Bean   // 1
        public CommandLineRunner initData(MongoOperations mongo) {  // 2
            return (String... args) -> {    // 3
                mongo.dropCollection(MyEvent.class);    // 4
                mongo.createCollection(MyEvent.class, CollectionOptions.empty().size(200).capped()); // 5
            };
        }
    
    1. 对于复杂的Bean只能通过Java Config的方式配置,这也是为什么Spring3之后官方推荐这种配置方式的原因,这段代码可以放到配置类中,本例我们就直接放到启动类WebFluxDemoApplication了;
    2. MongoOperations提供对MongoDB的操作方法,由Spring注入的mongo实例已经配置好,直接使用即可;
    3. CommandLineRunner也是一个函数式接口,其实例可以用lambda表达;
    4. 如果有,先删除collection,生产环境慎用这种操作;
    5. 创建一个记录个数为10的capped的collection,容量满了之后,新增的记录会覆盖最旧的。

    启动应用,我们检查一下event collection:

    watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2dldF9zZXQ=,size_16,color_FFFFFF,t_70

    OK,这个时候我们请求一下http://localhost:8080/events,发现立马返回了,并没有挂起。原因在于collection中一条记录都没有,而@Tailable起作用的前提是至少有一条记录。

    跑一下WebClient测试程序插入5条数据,然后再次请求:

    watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2dldF9zZXQ=,size_16,color_FFFFFF,t_70

    请求是挂起的,这没错,但是只有两条数据,看WebClient测试程序的控制台明明发出了5个请求啊。

    原因定义的CollectionOptions.empty().size(200).capped()中,size指的是以字节为单位的大小,并且会向上取到256的整倍数,所以我们刚才定义的是256byte大小的collection,所以最多容纳两条记录。我们可以这样改一下:

    CollectionOptions.empty().maxDocuments(200).size(100000).capped()
    

    maxDocuments限制了记录条数,size限制容量且是必须定义的,因为MongoDB不像关系型数据库有严格的列和字段大小定义,鬼知道会存多大的数据进来,所以容量限制是必要的。

    好了,再次启动应用,先插入5条数据,然后请求/events,收到5条记录后请求仍然挂起,在插入5条数据,curl客户端又会陆续收到新的数据。

    我们用代码搭建了图中箭头所表示的“管道”,看效果还是很畅通的嘛。现在再回想我们最初的那个Excel的例子,是不是感觉这个demo很有响应式的“范儿”了呢?

    1.3.3.6 总结

    这一节,我们对WebFlux做了一个简单的基于实例的介绍,相信你对响应式编程及其在WEB应用中如何发挥作用有了更多的体会,本章的实战是比较基础的,初衷是希望能够通过上手编写代码体会响应式编程的感觉,因为切换到响应式思维方式并非易事。

    这一章的核心关键词其实翻来覆去就是:“异步非阻塞的响应式流”。我们了解了异步非阻塞的好处,也知道如何让数据流动起来,下面我们就通过对实例的性能测试,借助实实在在的数据,真切感受一下异步非阻塞的“丝滑”。

    展开全文
  • 正则验证——常用的正则表达式

    千次阅读 2016-09-12 10:08:40
    说明:正则表达式通常用于种任务:1.验证,2.搜索/替换。用于验证时,通常需要在前后分别加上^和$,以匹配整个待验证字符串;搜索 /替换时是否加上此限定则根据搜索的要求而定,此外,也有可能要在前后加上\b而...
  • 人脸验证:Joint Bayesian

    千次阅读 2016-11-03 00:05:36
    《Bayesian Face Revisited: A Joint Formulation》论文解读这篇文章发表...概述经典的贝叶斯人脸识别方法是为个人脸的差异进行建模,但是作者看到这种为差异建模的方法降低了可分性。本文将个人脸进行联合建模,并
  • 1、如何解决SQL2000连接数据库失败及如何把windows身份验证模式改为混合验证模式。。。 由于SQL Server使用了"仅 Windows"的身份验证方式,因此用户无法使用SQL Server的登录帐户(如 sa )进行连接。解决方法如下所...
  • Spring编程和声明事务实例讲解

    千次阅读 2018-05-23 14:20:54
    历史回顾: 可能是最漂亮的Spring事务管理... Spring支持种方式的事务管理: 编程事务管理: 通过Transaction Template手动管理事务,实际应用中很少使用, 使用XML配置声明事务: 推荐使用(代码侵入...
  • 华为内部的关于IC验证的经验总结

    千次阅读 多人点赞 2020-04-30 12:10:45
    华为内部的关于IC验证的经验总结 ----IC验证工程师的易筋经 有人认为我验证做得很牛,也有人认为我的验证早就丢下了;有人认为我发现了各个项目的不少问题,也有人认为我在CMM库的几百个问题单大部分属纯净水...
  • 疫情期间游戏用户暴涨,黑客活动频率也在增长,想起R星俱乐部用户绑定两步验证给十金的长期活动实在是明智之举,下面说下橘子游戏平台origin二次验证码的绑定——预防黑客攻击~ 1.登陆橘子游戏平台,找到二次验证...
  • Java中的数据验证

    千次阅读 2018-12-12 18:19:00
    原文链接:https://www.cuba-platform.com/blog/2018-10-09/945 翻译:CUBA China CUBA-Platform 官网 : ... ...  我经常看见很多项目没有数据验证的策略和意识。他们的团队在交付日期的重压下...
  • 9.jQuery UI 验证插件

    千次阅读 2014-08-10 15:18:48
    验证插件(validate.js),是一款验证常规表单数据合法性的插件。使用它,极大 的解放了在表单上繁杂的验证过程,并且错误提示显示的完善也增加了用户体验。
  • 极验验证——滑块拼图验证码

    万次阅读 2016-09-13 09:55:25
    弹出需要绑定触发验证码弹出按钮   // 比如在登陆页面,这个触发按钮就是登陆按钮   captchaObj.bindOn( "#popup-submit" );       // 将验证码加到id为captcha的元素里 ...
  • ECDSA 签名验证原理及C语言实现

    万次阅读 多人点赞 2018-03-25 20:13:28
    天总算把ECDSA搞明白了,本来想造个ECDSA轮子,但最近有点忙,而ECDSA轮子又不像HASH那样简单,所以就直接拿现成的轮子来记录一些ECDSA学习心得。...非对称加密算法签名/验证无非包括三: 1. ...
  • JSF 转换与验证

    千次阅读 2005-10-11 11:20:00
    在本文中,我们将介绍 JSF 转换和验证框架的概念,它比您所想的要容易使用得多,也灵活得多。首先我们将介绍应用于 JSF 生命周期的转换和验证过程,然后展示一个简单的 JSF 应用程序中的默认转换和验证过程。接着将...
  • SystemC助力RTL测试平台验证TLM模块

    千次阅读 2006-03-24 16:16:00
    本文介绍开放设计和验证语言SystemC,通过该语言可实现RTL测试平台的复用,降低验证成本,缩短验证时间。 由于缺乏可靠的结构评估方法和软、硬件协同验证方法,系统结构设计工程师在设计系统级芯片(SoC)时,工作受...
  • ssh无密码验证登陆配置

    千次阅读 2016-02-19 10:21:45
    2、SSH无密码验证配置  Hadoop运行过程中需要管理远端Hadoop守护进程,在Hadoop启动以后,NameNode是通过SSH(Secure Shell)来启动和停止各个DataNode上的各种守护进程的。这就必须在节点之间执行指令的时候是...
  • 文本无关的声纹识别 验证

    千次阅读 2015-01-12 10:29:57
    文本无关的声纹识别 验证 By Dake Dake的专栏:www.glade.tk   一、声纹识别简介 声纹是指能惟一识别某人或某物的声音特征,是用电声学仪器显示的携带言语信息的声波频谱。虽然人的发音器官生理构造总的是...
  • 今天来分享一下,关于这...简易使用这第一种方式可谓是傻瓜的使用,我们只需要按照validation定义好的规则就可以了。 首先引入JQuery库和Validation插件: <script type="text/javascript" src="jquery-2.2.4.min.js
  • Forms验证中的roles

    千次阅读 2005-08-30 15:28:00
    一直对forms验证中的角色很模糊,不知道怎么搞,昨天晚上仔细看了下csdn的杂志,心里稍微有点底,今天早晨一上csdn,就看到思归大人回的一篇贴,是关于asp.net中的forms验证roles,地址是:...,特
  • Squid中文权威指南第12章-验证配置

    千次阅读 2012-10-11 16:34:10
    第12章 验证辅助器 先前我在6.1.2.12章里谈起过代理验证。然而,我仅仅解释了如何编写用于代理验证的访问控制规则。这里,我将告诉你如何选择和配置部分验证辅助器。 回想一下,Squid支持三种方式用于从用户端...
  • 人脸验证算法Joint Bayesian详解及实现(Python版) Tags: JointBayesian DeepLearning Python 本博客仅为作者记录笔记之用,不免有很多细节不对之处。 还望各位看官能够见谅,欢迎批评指正。 博客虽水,然...
  • 领英怎样跨成为全球领先的职业社交平台? 这些初创公司实现爆发成长的共同奥秘就是增长黑客。 增长黑客是硅谷当下热门的新商业方法论,其精髓在于通过快节奏测试和迭代,以极低甚至零成本获取并留存用户。 作为...
  • 在介绍Spring Validation验证框架之前,先看一下我们常用的校验注解都有哪些 限制 说明 @Null 限制只能为null @NotNull 限制必须不为null @AssertFalse 限制必须为false @AssertTrue 限制必须为true ...
  • 文本无关的声纹识别验证

    千次阅读 2013-09-16 11:23:57
    虽然人的发音器官生理构造总的是相同的,但人的语言产生是人体语言中枢与发音器官之间一个复杂的生理物理过程,人在讲话时使用的器官——舌、牙齿、喉头、肺、鼻腔在尺寸和形态等方面,每个人之间的差异会很大(见...
  • Squid第12章 验证辅助器

    千次阅读 2007-09-14 00:33:00
    原贴:http://www.opendigest.org/article.php/298 第12章 验证辅助器 12.1 配置Squid
  • ImageNet上,该模型取得了验证集top1 0.790和top5 0.945的正确率; ,该模型目前仅能以TensorFlow为后端使用,由于它依赖于”SeparableConvolution”层,目前该模型只支持channels_last的维度顺序(width, height, ...
  • K-折交叉验证(k-fold crossValidation)

    万次阅读 2017-12-25 18:41:57
    k-重交叉验证(k-fold crossValidation): 定义:  在机器学习中,将数据集A分为训练集B(training set)和测试集C(test set),在样本量不充足的情况下,为了充分利用数据集对算法效果进行测试,...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 70,035
精华内容 28,014
关键字:

两步式验证