【ASP.NET Core】按用户角色授权

上次老周和大伙伴们分享了有关按用户Level授权的技巧,本文咱们聊聊以用户角色来授权的事。

按用户角色授权其实更好弄,毕竟这个功能是内部集成的,多数场景下我们不需要扩展,不用自己写处理代码。从功能语义上说,授权分为按角色授权和按策略授权,而从代码本质上说,角色权授其实是包含在策略授权内的。怎么说呢?往下看。

角色授权主要依靠 RolesAuthorizationRequirement 类,来看一下源码精彩片段回放。

public class RolesAuthorizationRequirement : AuthorizationHandler<RolesAuthorizationRequirement>, IAuthorizationRequirement
{
    public RolesAuthorizationRequirement(IEnumerable<string> allowedRoles)
    {
        ……
        AllowedRoles = allowedRoles;
    }

    public IEnumerable<string> AllowedRoles { get; }

    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, RolesAuthorizationRequirement requirement)
    {
        if (context.User != null)
        {
            var found = false;

            foreach (var role in requirement.AllowedRoles)
            {
                // 重点在这里
                if (context.User.IsInRole(role))
                {
                    found = true;    //说明是符合角色要求的
                    break;
                }
            }

            if (found)
            {
                // 满足要求
                context.Succeed(requirement);
            }
        }
        return Task.CompletedTask;
    }

    ……
}

这个是不是有点熟悉呢?对的,上一篇博文里老周介绍过,实现 IAuthorizationRequirement 接口表示一个用于授权的必备条件(或者叫必备要素),AuthorizationHandler 负责验证这些必备要素是否满足要求。上一篇博文中,老周是把实现 IAuthorizationRequirement 接口和重写抽象类 AuthorizationHandler<TRequirement> 分成两部分完成,而这里,RolesAuthorizationRequirement 直接一步到位,两个一起实现。

好,理论先说到这儿,下面咱们来过一把代码瘾,后面咱们回过头来再讲。咱们的主题是说授权,不是验证。当然这两者通常是一起的,因为授权的前提是要验证通过。所以为了方便简单,老周还是选择内置的 Cookie 验证方案。不过这一回不搞用户名、密码什么的了,而是直接用 Claim 设置角色就行了,毕竟我们的主题是角色授权。

public class LoginController : Controller
{
    [HttpGet("/login")]
    public IActionResult Login() => View();

    [HttpPost("/login")]
    public async void Login(string role)
    {
        Claim c = new(ClaimTypes.Role, role);
        ClaimsIdentity id = new(new[] { c }, CookieAuthenticationDefaults.AuthenticationScheme);
        ClaimsPrincipal p = new ClaimsPrincipal(id);
        await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, p);
    }

    [HttpGet("/denied")]
    public IActionResult DeniedAcc() => Content("不好意思,你无权访问");

      [HttpGet("/logout")]
      public async void Logout()=> await HttpContext.SignOutAsync();

}

无比聪明的你一眼能看出,这是 MVC 控制器,并且实现登录有关的功能:

/login:进入登录页

/logout:注销

/denied:表白失败被拒绝,哦不,授权失败被拒绝后访问

Login 方法有两个,没参数的是 GET 版,有参数的是 POST 版。当以 POST 方式访问时,会有一个 role 参数,表示被选中的角色。这里为了简单,不用输用户名密码了,直接选个角色就登录。

Login 视图如下:

@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<div>
    <p>登录角色:</p>
    <form method="post">
        <select name="role">
            <option value="admin">管理员</option>
            <option value="svip" selected>超级会员</option>
            <option value="gen">普通客户</option>
        </select>
        <button type="submit">登入</button>
    </form>
</div>

select 元素的名称为 role,正好与 Login 方法(post)的参数 role 相同,能进行模型绑定。 

admin 角色表示管理员,svip 角色表示超级VIP客户,gen 角色表示普通客户。假设这是一家大型纸尿裤批发店的客户管理系统。这年头,连买纸尿裤也要分三六九等了。

下面是该纸尿裤批发店为不同客户群提供的服务。

[Route("znk")]
public class 纸尿裤Controller : Controller
{
    [Route("genindex")]
    [Authorize(Roles = "gen")]
    public IActionResult IndexGen()
    {
        return Content("普通客户浏览页");
    }

    [Route("adminindex")]
    [Authorize(Roles = "admin")]
    public IActionResult IndexAdmin()
    {
        return Content("管理员专场");
    }

    [Route("svipindex")]
    [Authorize(Roles = "svip")]
    public IActionResult IndexSVIP() => Content("超级会员杀熟通道");
}

注意上面授权特性,不需要指定策略名称,只需指定你要求的角色名称即可。

在应用程序的初始化配置上,咱们设置 Cookie 验证。

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(opt =>
{
    opt.LoginPath = "/login";
    opt.AccessDeniedPath = "/denied";
    opt.LogoutPath = "/logout";
    opt.ReturnUrlParameter = "url";
    opt.Cookie.Name = "_XXX_FFF_";
});
var app = builder.Build();

那几个路径就是刚才 Login 控制器上的访问路径。

因为不需要配置授权策略,所以不需要调用 AddAuthorization 扩展方法。主要是这个方法你在调用 AddControllersWithViews 方法时会自动调用,所以,如无特殊配置,咱们不用手动开启授权功能。像 MVC、RazorPages 等这些功能,默认会配置授权的。

假如我要访问纸尿裤批发店的超级会员通道,访问 /znk/svipindex,这时候会跳转到登录界面,并且 url 参数包含要回调的路径。

 

默认是选中“超级会员”的,此时点击“登入”,就能获取授权。

 

如果选择“普通客户”,就会授失败,拒绝访问。

 

----------------------------------------------------------------------------------------

虽然角色授权功能咱们轻松实现了,可是,随之而来的会产生一些疑问。不知道你有没有这些疑问,反正老周有。

1、既然在代码上角色授权是包含在策略授权中的,那咱们没配置策略啊,为啥不出错?

AuthorizationPolicy 类有个静态方法—— CombineAsync,这个方法的功能是合并已有的策略。但,咱们重点看这一段:

AuthorizationPolicyBuilder? policyBuilder = null;
if (!skipEnumeratingData)
{
    foreach (var authorizeDatum in authorizeData)
    {
        if (policyBuilder == null)
        {
            policyBuilder = new AuthorizationPolicyBuilder();
        }

        var useDefaultPolicy = !(anyPolicies);
        // 如果有指定策略名称,就合并
        if (!string.IsNullOrWhiteSpace(authorizeDatum.Policy))
        {
            var policy = await policyProvider.GetPolicyAsync(authorizeDatum.Policy).ConfigureAwait(false);
            if (policy == null)
            {
                throw new InvalidOperationException(Resources.FormatException_AuthorizationPolicyNotFound(authorizeDatum.Policy));
            }
            policyBuilder.Combine(policy);
            useDefaultPolicy = false;
        }

        // 如果指定了角色名称,调用 RequireRole 方法添加必备要素
        var rolesSplit = authorizeDatum.Roles?.Split(',');
        if (rolesSplit?.Length > 0)
        {
            var trimmedRolesSplit = rolesSplit.Where(r => !string.IsNullOrWhiteSpace(r)).Select(r => r.Trim());
            policyBuilder.RequireRole(trimmedRolesSplit);
            useDefaultPolicy = false;
        }

        // 同理,如果指定的验证方案,添加之
        var authTypesSplit = authorizeDatum.AuthenticationSchemes?.Split(',');
        if (authTypesSplit?.Length > 0)
        {
            foreach (var authType in authTypesSplit)
            {
                if (!string.IsNullOrWhiteSpace(authType))
                {
                    policyBuilder.AuthenticationSchemes.Add(authType.Trim());
                }
            }
        }
 ……

原来,在合并策略过程中,会根据 IAuthorizeData 提供的内容动态添加 IAuthorizationRequirement 对象。这里出现了个 IAuthorizeData  接口,这厮哪来的?莫急,你看看咱们刚才在 纸尿裤 控制器上应用了啥特性。

 [Route("adminindex")]
 [Authorize(Roles = "admin")]
 public IActionResult IndexAdmin()
 {
     return Content("管理员专场");
 }

对,就是它!AuthorizeAttribute,你再看看它实现了什么接口。

public class AuthorizeAttribute : Attribute, IAuthorizeData

再回忆一下刚刚这段:

 var rolesSplit = authorizeDatum.Roles?.Split(',');
 if (rolesSplit?.Length > 0)
 {
     var trimmedRolesSplit = rolesSplit.Where(r => !string.IsNullOrWhiteSpace(r)).Select(r => r.Trim());
     policyBuilder.RequireRole(trimmedRolesSplit);
     ……
 }

原来这里面还有玄机,Role 可以指定多个角色的哟,用逗号(当然是英文的逗号)隔开。如

 [Route("adminindex")]
 [Authorize(Roles = "admin, svip")]
 public IActionResult IndexAdmin()
 {
     ……
 }

 

2、我没有在中间件管道上调用 app.UseAuthorization(),为什么能执行授权处理?

你会发现,在 app 上不调用 UseAuthorization 扩展方法也能使授权生效。因为像 RazorPages、MVC 这些东东还有一个概念,叫 Filter,可以翻译为“筛选器”或“过滤器”。老周比较喜欢叫过滤器,因为这叫法生动自然,筛选器感觉是机器翻译。

在过滤器里,有专门用在授权方面的接口。

同步:IAuthorizationFilter

异步:IAsyncAuthorizationFilter

在过滤器中,同步接口和异步接口只实现其中一个即可。如果你两个都实现了,那只执行异步接口。所以,你两个都实现纯属白淦,毕竟异步优先。为啥?你看看 ResourceInvoker 类的源代码就知道了。

 switch (next)
 {
     case State.InvokeBegin:
         {
             goto case State.AuthorizationBegin;
         }

     case State.AuthorizationBegin:
         {
             _cursor.Reset();
             goto case State.AuthorizationNext;
         }

     case State.AuthorizationNext:
         {
             var current = _cursor.GetNextFilter<IAuthorizationFilter, IAsyncAuthorizationFilter>();
             if (current.FilterAsync != null)  // 执行异步方法
             {
                 if (_authorizationContext == null)
                 {
                     _authorizationContext = new AuthorizationFilterContextSealed(_actionContext, _filters);
                 }

                 state = current.FilterAsync;
                 goto case State.AuthorizationAsyncBegin;
             }
             else if (current.Filter != null) // 执行同步方法
             {
                 if (_authorizationContext == null)
                 {
                     _authorizationContext = new AuthorizationFilterContextSealed(_actionContext, _filters);
                 }

                 state = current.Filter;
                 goto case State.AuthorizationSync;
             }
             else
             {
          // 如果都不是授权过滤器,直接 End
goto case State.AuthorizationEnd; } } case State.AuthorizationAsyncBegin: { Debug.Assert(state != null); Debug.Assert(_authorizationContext != null); var filter = (IAsyncAuthorizationFilter)state; var authorizationContext = _authorizationContext; _diagnosticListener.BeforeOnAuthorizationAsync(authorizationContext, filter); _logger.BeforeExecutingMethodOnFilter( FilterTypeConstants.AuthorizationFilter, nameof(IAsyncAuthorizationFilter.OnAuthorizationAsync), filter); var task = filter.OnAuthorizationAsync(authorizationContext); if (!task.IsCompletedSuccessfully) { next = State.AuthorizationAsyncEnd; return task; } goto case State.AuthorizationAsyncEnd; } case State.AuthorizationAsyncEnd: { Debug.Assert(state != null); Debug.Assert(_authorizationContext != null); var filter = (IAsyncAuthorizationFilter)state; var authorizationContext = _authorizationContext; _diagnosticListener.AfterOnAuthorizationAsync(authorizationContext, filter); _logger.AfterExecutingMethodOnFilter( FilterTypeConstants.AuthorizationFilter, nameof(IAsyncAuthorizationFilter.OnAuthorizationAsync), filter); if (authorizationContext.Result != null) { goto case State.AuthorizationShortCircuit; } // 完成后直接下一个授权过滤器 goto case State.AuthorizationNext; } case State.AuthorizationSync: { Debug.Assert(state != null); Debug.Assert(_authorizationContext != null); var filter = (IAuthorizationFilter)state; var authorizationContext = _authorizationContext; _diagnosticListener.BeforeOnAuthorization(authorizationContext, filter); _logger.BeforeExecutingMethodOnFilter( FilterTypeConstants.AuthorizationFilter, nameof(IAuthorizationFilter.OnAuthorization), filter); filter.OnAuthorization(authorizationContext); _diagnosticListener.AfterOnAuthorization(authorizationContext, filter); _logger.AfterExecutingMethodOnFilter( FilterTypeConstants.AuthorizationFilter, nameof(IAuthorizationFilter.OnAuthorization), filter); if (authorizationContext.Result != null) { goto case State.AuthorizationShortCircuit; } // 完成后直接一下授权过滤器 goto case State.AuthorizationNext; } case State.AuthorizationShortCircuit: { Debug.Assert(state != null); Debug.Assert(_authorizationContext != null); Debug.Assert(_authorizationContext.Result != null); Log.AuthorizationFailure(_logger, (IFilterMetadata)state); // This is a short-circuit - execute relevant result filters + result and complete this invocation. isCompleted = true; _result = _authorizationContext.Result; return InvokeAlwaysRunResultFilters(); } case State.AuthorizationEnd: { goto case State.ResourceBegin; }

代码很长,老周总结一下它的执行轨迹:

1、AuthorizationBegin 授权开始

2、AuthorizationNext 下一个过滤器

3、如果是异步,走 AuthorizationAsyncBegin

      如果同步,走 AuthorizationSync

      如果都不是,直接走到 AuthorizationEnd

4、异步:AuthorizationAsyncBegin --> AuthorizationAsyncEnd --> AuthorizationNext(回第2步,有请下一位过滤侠)

      同步:AuthorizationSync --> AuthorizationNext(回第2步,有请下一位)

5、AuthorizationEnd 退场,进入 ResourceFilter 主会场

6、在2、3、4步过程中,如果授权失败或出错,直接短路,走 AuthorizationShortCircuit

你瞧,是不是同步和异步只执行一个?

默认的授权过滤器实现 IAsyncAuthorizationFilter,即 AuthorizeFilter 类。所以,授权处理就是在这里被触发了。

var policyEvaluator = context.HttpContext.RequestServices.GetRequiredService<IPolicyEvaluator>();

// 先进行验证
var authenticateResult = await policyEvaluator.AuthenticateAsync(effectivePolicy, context.HttpContext);

// 如果允许匿名访问,后面的工作就免了
if (HasAllowAnonymous(context))
{
    return;
}

// 验证过了,再评估授权策略
var authorizeResult = await policyEvaluator.AuthorizeAsync(effectivePolicy, authenticateResult, context.HttpContext, context);

if (authorizeResult.Challenged) //没登录呢,去登录
{
    context.Result = new ChallengeResult(effectivePolicy.AuthenticationSchemes.ToArray());
}
else if (authorizeResult.Forbidden)  //授权失败,拒绝访问
{
    context.Result = new ForbidResult(effectivePolicy.AuthenticationSchemes.ToArray());
}

但是,这个授权过滤器在 MvcOptions 的 Filters 中没有啊,它是啥时候弄进去的?这货不是在 Filters 中配置的,而是在 Application Model 初始化时通过 AuthorizationApplicationModelProvider 类弄进去的。AuthorizationApplicationModelProvider 类实现了 IApplicationModelProvider 接口,但不对外公开。

 public void OnProvidersExecuting(ApplicationModelProviderContext context)
 {
     if (context == null)
     {
         throw new ArgumentNullException(nameof(context));
     }

     if (_mvcOptions.EnableEndpointRouting)
     {
         // When using endpoint routing, the AuthorizationMiddleware does the work that Auth filters would otherwise perform.
         // Consequently we do not need to convert authorization attributes to filters.
         return;
     }

     foreach (var controllerModel in context.Result.Controllers)
     {
         var controllerModelAuthData = controllerModel.Attributes.OfType<IAuthorizeData>().ToArray();
         if (controllerModelAuthData.Length > 0)
         {
             controllerModel.Filters.Add(GetFilter(_policyProvider, controllerModelAuthData));
         }
         foreach (var attribute in controllerModel.Attributes.OfType<IAllowAnonymous>())
         {
             controllerModel.Filters.Add(new AllowAnonymousFilter());
         }

         foreach (var actionModel in controllerModel.Actions)
         {
             var actionModelAuthData = actionModel.Attributes.OfType<IAuthorizeData>().ToArray();
             if (actionModelAuthData.Length > 0)
             {
                 actionModel.Filters.Add(GetFilter(_policyProvider, actionModelAuthData));
             }

             foreach (var _ in actionModel.Attributes.OfType<IAllowAnonymous>())
             {
                 actionModel.Filters.Add(new AllowAnonymousFilter());
             }
         }
     }
 }

而 filter 是在 GetFilter 方法生成的。

    public static AuthorizeFilter GetFilter(IAuthorizationPolicyProvider policyProvider, IEnumerable<IAuthorizeData> authData)
    {
        // The default policy provider will make the same policy for given input, so make it only once.
        // This will always execute synchronously.
        if (policyProvider.GetType() == typeof(DefaultAuthorizationPolicyProvider))
        {
            var policy = AuthorizationPolicy.CombineAsync(policyProvider, authData).GetAwaiter().GetResult()!;
            return new AuthorizeFilter(policy);
        }
        else
        {
            return new AuthorizeFilter(policyProvider, authData);
        }
    }

 

3、RolesAuthorizationRequirement 实现了 IAuthorizationHandler 接口,可是它又没注册到服务容器中,HandlerAsync 方法又是怎么调用的?

RolesAuthorizationRequirement 一步到位,既实现了 IAuthorizationRequirement 接口又实现抽象类 AuthorizationHandler<TRequirement>。它虽然没有在服务容器中注册,可服务容器中注册了 PassThroughAuthorizationHandler 类,有它在,各种实现 IAuthorizationHandler 接口的 Requirement 都能顺利执行,看看源代码。

public class PassThroughAuthorizationHandler : IAuthorizationHandler
{
    ……

    public async Task HandleAsync(AuthorizationHandlerContext context)
    {
        foreach (var handler in context.Requirements.OfType<IAuthorizationHandler>())
        {
            await handler.HandleAsync(context).ConfigureAwait(false);
            if (!_options.InvokeHandlersAfterFailure && context.HasFailed)
            {
                break;
            }
        }
    }
}

看,这不就执行了吗。

至此,咱们就知道这角色授权的流程是怎么走的了。

本文转载于网络 如有侵权请联系删除

相关文章

  • Docker基本使用命令

    前言刚看别人使用Docker的时候有很多不解,为什么要用Docker,Docker怎么用?Docker配置为什么这么难?为什么网络访问不通?等等因素阻碍了笔者学习Docker?其实笔者也很笨,有很多思考不清晰的点。顺便也分享下。学时疑惑:Q:我一套服务为什么不放在一个容器里面(Java、MySQL、Nginx、Redis等)?A:因为既要维护容器内网络,又要维护端口等等之类的东西,Docker就是为了快速搭建环境而生的,而且Docker最好也是一个服务一个容器,这样好打理。Q:Docker能放到生产环境吗?A:在公司没有专门的运维团队情况下,不建议使用Docker部署的环境作为生产环境,因为不仅仅要维护项目和中间件,Docker或者Kubernetes出现问题后,还要解决这些问题,也就是还要解决Docker的问题。如果在没有专门运维团队的情况下,最好使用某里云的服务,例如RDS,SLB等,最起码别人还会帮你维护你的数据库和服务。基本命令下载镜像#以Redis为例子 dockerpullredis复制运行镜像dockerrun\ -d\#后台运行 --nameredis6\#自定义名字

  • 轻松搞定Unix/Linux环境使用

    1Unix/Linux操作系统介绍1.1操作系统的作用1.1.1操作系统的目标方便:使计算机系统易于使用有效:以更有效的方式使用计算机系统资源扩展:方便用户有效开发、测试和引进新功能1.1.2操作系统的地位操作系统在计算机系统中承上启下的地位:向下封装硬件,向上提供操作接口。1.2Unix/Linux操作系统介绍1.2.1Unix家族1965:贝尔实验室(BellLabs)加入一项由通用电气和麻省理工学院合作的计划,该计划要建立一套多使用者、多任务、多层次的MULTICS操作系统。后来因为项目太为复杂失败。1969:其主要开发者Thompson(后被称为UNIX之父)和Ritchie领导一组开发者,开发了一个新的多任务操作系统—UNICS,后来被改名为Unix,最初的Unix是用B语言和汇编语言混合编写而成。1971:两人在贝尔实验室共同发明了C语言,并于1973用C语言重写了Unix。1974:UNIX第一次出现在贝尔实验室以外。此后UNIX被政府机关,研究机构,企业和大学注意到,并逐渐流行开来。1980:有两个最主要的Unix的版本线,一个是Berkeley的BSDUNIX,另一个

  • 如何利用Bash脚本监控Linux的内存使用情况

    前言目前市场上有许多开源监控工具可用于监控Linux系统的性能。当系统达到指定的阈值限制时,它可以发送电子邮件警报。它可以监视CPU利用率、内存利用率、交换利用率、磁盘空间利用率等所有内容。如果你只有很少的系统并且想要监视它们,那么编写一个小的shell脚本可以使你的任务变得非常简单。在本教程中,我们添加了两个shell脚本来监视Linux系统上的内存利用率。当系统达到给定阈值时,它将给特定电子邮件地址发邮件。方法-1:用LinuxBash脚本监视内存利用率并发送电子邮件 如果只想在系统达到给定阈值时通过邮件获取当前内存利用率百分比,请使用以下脚本。这是个非常简单直接的单行脚本。在大多数情况下,我更喜欢使用这种方法。当你的系统达到内存利用率的80%时,它将触发一封电子邮件。*/5****/usr/bin/free|awk'/Mem/{printf("RAMUsage:%.2f%\n"),$3/$2*100}'|awk'{print$3}'|awk'{if($1>80)print$0;}'|mail-s

  • 总结:DCIC算法分析赛完整方案分享!

    每日干货&每月组队学习,不错过Datawhale干货 作者:阿水,北航计算机硕士,Datawhale成员DCIC2020本文将以DCIC2020赛道二《巡游车与网约车运营特征对比》为具体内容,讲解赛题介绍、赛题理解、赛题任务解析、赛题数据介绍和赛题指标计算。比赛地址:https://data.xm.gov.cn/opendata-competition/#/contest_explain本文会给出赛题二直接可以提交的代码思路,全文阅读需要10分钟。 赛题介绍 赛题名称:A城市巡游车与网约车运营特征对比分析赛题说明:出租车作为城市客运交通系统的重要组成部分,以高效、便捷、灵活等优点深受居民青睐。出租车每天的运营中会产生大量的上下车点位相关信息,对这些数据进行科学合理的关联和挖掘,对比在工作日以及休息日、节假日的出租车数据的空间分布及其动态变化,对出租车候车泊位、管理调度和居民通勤特征的研究具有重要意义。出租车/网约车:上下车地点挖掘;出租车/网约车:不同日期的空间变化;出租车/网约车:泊车和调度问题;赛题任务:参赛者需依据赛事方提供的出租车(包括巡游车和网约车)GPS和订单数据统

  • 一文囊括图像处理25个高频考点

    重磅干货,第一时间送达 来源:深度学习与计算机视觉介绍 从非结构化数据中提取有用的信息一直是研究界极为关注的话题。图像就是一种这样的非结构化数据,图像数据分析在商业的各个方面都有应用。此技能测试是专门为你设计的,旨在测试你如何处理图像数据的知识,重点是图像处理。300多人报名参加了考试,如果你是错过这项技能测试的人之一,那么这篇文章为你介绍了问题和对应的解决方案。这是参加考试的参与者的排行榜。https://datahack.analyticsvidhya.com/contest/image-skilltest/lb有用的资源这里有一些资源可以深入了解该主题。机器学习算法的要点(带有Python和R代码)https://www.analyticsvidhya.com/blog/2017/09/common-machine-learning-algorithms/Python图像处理的基础https://www.analyticsvidhya.com/blog/2014/12/image-processing-python-basics/技能测试问答1)将以下图像格式匹配到正确的通道数灰

  • Data Vault 简介

    DataVault简介DataVault2.0不仅是建模技术,也提供了一整套数据仓库项目的方法论。它能提供一套非常可行的方案来满足数据仓库项目中对于历史轨迹和审核两个方面的需求。多年来,商业智能(BI)项目一直并将继续在瀑布模型下运行。它是由每个阶段的长时间延伸的序列定义的,该序列需要一份详尽的前期需求列表、一个完整的数据模型设计,然后将所有硬业务规则和软业务规则编入ETL流程。可视化层是按顺序构建的,并从最初的开始日期算起,在几个月甚至几年之后提交给最终用户。我们经常看到团队采用“缩小范围”的瀑布模式,目的是将大型BI计划分解成较小的项目。虽然有助于降低整体的复杂性,但是这种方法在应用于BI时仍然有很大的风险,因为有两个主要的问题:业务需求的变化速度远快于BI研发的交付能力;预算不愿意花在没有短期利益的长期项目.以上两个原因就是为什么我们设计模式从瀑布转向可迭代敏捷模式,这种模式提供了一些方法来解决问题。但是在数据分析领域,敏捷本身并不能解决我们在更详细的数据仓库或BI项目级别上遇到的重大挑战。这些包括:迭代数据建模减少重构设计ETL或ELT流程,使其能够快速响应业务逻辑的变化或新增

  • 大促迷思:那个榨干我钱包的“猜你喜欢”是什么来头!?

    //永远别问我们双11花掉多少钱////包裹有多重,心里有多痛//双11刚刚过去,双12即将到来,不知大家的手是否还在?经历过某猫某东某宝拼杀的各位买家,大概都有过被这些平台猜透小心思,“看了又看、买了又买”的经历。它们在偷看你的生活吗,为什么总能直击你的心房,让你不由自主的献出积蓄呢?今天,我们深扒一下那些“猜你喜欢”背后的势力——推荐系统算法中的元老级算法:基于物品的协同过滤算法。不管你在“双11”还是“618”这样的“商造节日”中有没有剁过手,对“看了该商品的人还看了”这样的推荐形式也一定不陌生。无论是淘宝还是京东,抑或其他电商网站,这样的推荐形式都可以算是标配。类似的还有“喜欢这部电影的人还喜欢”,“关注了TA的人还关注了”,等等。这样的推荐形式都来自一个“古老”的推荐算法——基于物品的协同过滤。基于物品的协同过滤诞生于1998年,由亚马逊首先提出。2001年,其发明者发表了相应的论文。这篇论文在Google学术上的引用数已经近7000次,并且在WWW2016大会上荣获“时间检验奖”,颁奖词是:“这篇杰出的论文深深地影响了实际应用。”历经15年,它仍然在发光发热,显然这个奖它当

  • 2019今日头条年度数据报告(完整版)

    ▼数据猿公告▼数据猿即将推出“2020上半年度大型主题策划活动——我的产品观”,敬请期待!大数据产业创新服务媒体——聚焦数据·改变商业点击阅读原文下载PDF电子版报告——END——阅读原文:https://index.toutiao.com/pdfjs/view.html?file=//index.toutiao.com/report/download/18fa1a42899f3aa4b534a055d51bd31b.pdf

  • C语言——小学三年级题目解析(二)

    作者|我是奔跑的键盘侠来源|奔跑的键盘侠(ID:runningkeyboardhero)转载请联系授权(微信ID:ctwott)搬砖继续……终于,到了A卷最后一期了,不容易啊。这期可能解析稍微详细了一点,耗费时间精力不少,B卷可能会简略一些。大家如果有问题可以留言。年级只是代表题型,难度不做区分。规则:一年级:选择题;二年级:填空题;三年级:阅读题;四年级:编程题。第4题首先定义了几个变量,sum=0,初始化一个6x6的二维数组、整型。然后两层嵌套循环输入数组的值,先第一行6个,再第二行……一句话:根据用户输入的整数,构造了一个6x6的二维数组。 后面一个两层for循环,是用来求和的,sum=sum+array[i][j]。看清楚i,j的始末,也就知道了是求哪些元素的和了。 i从1开始,也就是第二行,到5结束,也就是从第二行到第六行;j从0开始到5,也就是所有的列。if语句,i==j是对角线上的元素,对应的是1,1;2,2;3,3;4,4;5,5;这5个元素。而i+j==5,是另外一个对角线,对应的1,4;2,3;3,2;4,1;5,0画个图吧,更形象一点:也就是图中的黄色单元格的值,

  • 马斯克和贝索斯的“星球大战”

    大数据文摘出品编译:籍缓、张南星、李雷、王嘉仪商业太空计划已经蓬勃发展了十年。在这个领域中,最雄心勃勃的两家公司当属BlueOrigin(蓝色起源)和SpaceX(太空探索技术公司),而它们之间的竞争一直是众人的焦点。乍一看,这两家公司很像,他们都是互联网独角兽:BlueOrigin的杰夫·贝索斯(JeffBezos)创办了全球最大的网上书店亚马逊,而SpaceX的伊隆·马斯克(ElonMusk)则是以创办在线业务起家,其中最出名的是Paypal。年少的贝索斯年少的马斯克两家公司都在开发大型可重复使用的运载火箭,为政府和商业客户提供载人和卫星发射业务。他们的初衷都是人类的太空移民梦想。在未来的一年里,两位巨头的竞争也许会给我们带来几出重头戏。在过去几年中,SpaceX已成为地球上最活跃的卫星发射公司之一。仅在2018年,SpaceX就进行了20次发射(截至发稿时),还有两次计划在年底之前完成,总发射次数约占全球的20%。SpaceX特立独行,是至今唯一一个能重复使用其火箭的公司。它的猎鹰9号一级火箭从一开始的经常性着陆坠毁到后来成为发射任务的主力。去年五月,SpaceX推出了最新版本的

  • python学习笔记5.3-包的创建

    包,也可以称为库,是具有很多功能的一个集合体。本文主要介绍如何自己创建一个包,以及介绍一些在包的创建过程中的技巧。1.包的创建本文的例子将使用最复杂的情况,也就是包目录下含有许多子包,子包中包含许多模块。/project /subpjt1 __init__.py a.py b.py /subpjt2 __init__.py c.py d.py __init__.py test.py复制test.py文件是我们在包外运行的文件,包名定义为project,包含两个子包subpjt1和subpjt2,两个子包分别包含a,b和c,d两个模块。我们注意到每个包目录都有__init__.py这个文件,稍后会讲解它的作用,a.py中有一下代码:deffunc_a(): print('thisisfunca')复制此时所有的__init__.py文件中都为空,不写任何内容。调用func_a:fromproject.subpjt1.aimportfunc_a importproject.subpjt1.a.func_a#与上一句一样的功能 func_a()复制以上是最普通的做法,

  • Sh备份数据库

    sh脚本备份数据库#!/bin/bash BACKUP=/u01/backup/MySQL/ DATETIME=$(date'+%Y-%m-%d') DB_HOST=127.0.0.1 DB_USER=root DB_PWD=root DATABASE1=test DATABASE2=test_api mkdir-p"${BACKUP}/$DATETIME" TIME=$(date'+%H%M%S') mysqldump-u${DB_USER}-p${DB_PWD}-h${DB_HOST}-q-R--databases$DATABASE1|gzip>${BACKUP}/$DATETIME/${TIME}_${DATABASE1}.sql.gz mysqldump-u${DB_USER}-p${DB_PWD}-h${DB_HOST}-q-R--databases$DATABASE2|gzip>${BACKUP}/$DATETIME/${TIME}_${DATABASE2}.sql.gz复制crontab定时任务语法可

  • 从零开始配置vim(25)——关于 c++ python 的配置

    从9月份到国庆这段时间,因为得了女儿,于是回老家帮忙料理家事以及陪伴老婆和女儿。一时之间无暇顾及该系列教程的更新。等我回来的时候发现很多小伙伴私信我催更。在这里向支持本人这一拙劣教程的各位小伙伴表示真诚的感谢。言归正传,让我们开始吧之前我们根据lua语言配置了基于lsp的代码高亮、自动跳转、自动补全等等功能,那个时候我们安装了很多插件,像nvim-lspconfig、nvim-lsp-installernvim-cmp等等,每个插件都在干嘛,虽然我们配置好了lua相关的内容,但是可能仍然有小伙伴有疑问,碰到其他语言该如何配置,是不是要重新下载对应的插件呢?为了解答这些问题,这篇文章我们将要来根据c++和python的日常习惯来进行配置,给大家演示一下在上述内容都配置完成之后面对其他语言我们该如何进行处理安装配置c++相关的lsp服务关于c++的服务,我们根据nvim-lsp-installer官方给出的表格中显示它可以使用ccls和clang,这里我们以ccls作为示例进行讲解。首先通过命令安装:LspInstallccls接着我们新建一个ftplugin/c.lua和ftplugin

  • 集合框架-ArrayList学习和总结

    一、ArrayList 贴上关键代码,有些方法做了合并,方便逻辑理解 publicclassArrayListextendsAbstractListimplementsList,RandomAccess,Cloneable,java.io.Serializable{ /** *记录ArrayList的size被改变的次数,在父类AbstractList中定义 *为了提供快速失败机制:在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了修改(增加,删除,修改),则会抛出ConcurrentModificationException *典型的遍历一个集合删除某个元素时,应使用Iterator.remove(),避免使用List.remove(intindex) */ protectedtransientintmodCount=0; /** *size的大小和实际数组中的元素个数一致 */ privateintsize; /** *elementData.length是随着扩容变化的,和size不是一样的 *弄清楚minCapacity,size,elementData.l

  • 一本通1299 糖果

    题目传送门 思路 首先一眼有了一个状态:设\(f_{i,j}\)表示前\(i\)个数能否得到\(j\),如果可以那么就是\(1\),反之为\(0\)。这样状态转移方程也非常好写:\(f_{i+1,j+a_i}=max(f_{i+1,j+a_i},f_{i,j})\),然后再注意一下状态的继承即可。 但是这样设计状态明显是被空间和时间限制的,那么到底应该怎样设计状态呢?首先我们可以发现,上个状态的设计跟\(k\)的取值其实毫无关联,但是实际\(k\)的范围很小,所以我们相当于浪费了一个条件。由于\(k\)的范围很小,而最后的答案又要整除\(k\)。所以可以设\(f_{i,j}\)表示前\(i\)个数除以\(k\)的余数是\(j\)时的最大答案。那么最后输出\(f_{n,0}\)即可。中间状态转移方程也很简单,一个个数往前递推即可。但是注意细节就是一定要继承之前已经找到的状态,即\(f_{i,j}=max(f_{i,j},f_{i-1,j})\),用语言来描述就是前\(i-1\)个数能找到的最大值,在前\(i\)个数中一定也能找到。 Code #include<iostream>

  • HDU - 7084 Pty loves string

    tag:border树,树状数组,二维数点 首先前缀x和后缀y拼起来等价于存在一点p,满足\(s[1,x]=s[p-x+1,p]\ands[p+1,p+y+1]=s[n-y+1,n]\),发现这其实是p和p+1两个位置在正串和反串中的border border有个性质,border的border可以遍历当前点的所有border(画个大border和小border的图就明白了),所以根据这个性质,我们可以建个border树,分别满足两个条件的p就在正串和反串分别建立的border树对应节点的子树内。 所以现在就变成了两棵树中两个子树分别有多少个点满足x+1=y,对其中一个树特殊处理一下,其实就变成了子树内有多少个节点权值相同,也就转化成了二维数点问题。 因为是在子树中,所以有dfs序连续且嵌套的关系,可以把询问离线下来,直接两次dfs然后用bit统计子树中发生的增量(具体实现是一开始减掉然后后面加上) 如果不这么麻烦的话也可以二维数点的通常方法,离线变成二维前缀和的简单容斥,或者在线上主席树这样。 #include<bits/stdc++.h> usingnamespaces

  • github使用-知乎的某小姐的一篇文章

    作者:珊姗是个小太阳链接:http://www.zhihu.com/question/20070065/answer/79557687来源:知乎著作权归作者所有,转载请联系作者获得授权。 作为一个文科妹子,我在看过几乎所有热门github教程之后依旧一头雾水,在近半年的摸索中终于明白啦~新年初,把自己纯小白的学习经验分享一下吧!#什么是Github?必须要放这张图了!!!&lt;imgsrc="https://pic4.zhimg.com/7c9d3403bf922b1663f56975869c829b_b.png"data-rawwidth="600"data-rawheight="412"class="origin_imagezh-lightbox-thumb"width="600"data-original="https://pic4.zhimg.com/7c9d3403bf922b1663f56975869c829b_r.png"&gt;(图片来源(图片来源GitHub是怎样的一个存在?-DeepReader的回答) Git是由Linux之父LinusTova

  • Ubuntu安装Redis及使用

    NoSQL简介NoSQL,全名为NotOnlySQL,指的是非关系型的数据库随着访问量的上升,网站的数据库性能出现了问题,于是nosql被设计出来 优点/缺点优点:高可扩展性分布式计算低成本架构的灵活性,半结构化数据没有复杂的关系 缺点:没有标准化有限的查询功能(到目前为止)最终一致是不直观的程序 分类 类型 部分代表 特点 列存储 Hbase、Cassandra、Hypertable 顾名思义,是按列存储数据的。最大的特点是方便存储结构化和半结构化数据,方便做数据压缩,对针对某一列或者某几列的查询有非常大的IO优势。文档存储 MongoDB、CouchDB 文档存储一般用类似json的格式存储,存储的内容是文档型的。这样也就有有机会对某些字段建立索引,实现关系数据库的某些功能。key-value存储 TokyoCabinet/Tyrant、BerkeleyDB、MemcacheDB、Redis 可以通过key快速查询到其value。一般来说,存储不管value的格式,照单全收。(Redis包含了其他功能)图存储 Neo4J、FlockDB 图形关系的最佳存储。使用传统关系数据库来解决

  • java上传图片到虚拟路径,简单反射

    1.配置tomcat,还有依赖包 进入配置。 选择一个空文件夹作为虚拟路径,配置好下载所需的jar包,点击这可下载 起名字最好是:commons-beanutils-1.8.0 点击+,导入刚刚的五个jar包 2.编写UploadServlet 点击查看的代码 importorg.apache.commons.fileupload.FileItem; importorg.apache.commons.fileupload.FileItemFactory; importorg.apache.commons.fileupload.FileUploadException; importorg.apache.commons.fileupload.disk.DiskFileItemFactory; importorg.apache.commons.fileupload.servlet.ServletFileUpload; importjavax.servlet.annotation.WebServlet; importjavax.servlet.http.Http

  • 博客园定制CSS代码 设置页面宽度

    博客园定制CSS代码,设置页面宽度 下面CSS代码中,width项的值控制页面宽度 #home{ //opacity:1.0; margin:0auto; width:75%; min-width:70%; //background-color:#fff; padding:30px; margin-top:30px; margin-bottom:50px; //box-shadow:02px6pxrgba(100,100,100,0.3); } 复制

  • HDU 6406 Taotao Picks Apples 线段树维护

    题意:给个T,T组数据; 每组给个n,m;n个数,m个操作; (对序列的操作是,一开始假设你手上东西是-INF,到i=1时拿起1,之后遍历,遇到比手头上的数量大的数时替换(拿到手的算拿走),问最后拿走几个) 每次操作是将p位变为q;问此时序列能拿走几个数;   思路:假设p位变了,不管变大变小,我们都得知道一件事,就是要找到在p之前最长的序列;   因为这个是不变的,可以预处理,所以说我们就在第i位置记录1~i的最大值,和最大取走的数量以及最大数的id,这样就可以知道p-1位置的信息就是1~p-1位置最大的数的信息,O(1)得到;   (如果q比1~p-1最大的数小,那么就将q变为那个最大的数,因为这样我们就可以不用分情况去查询)   之后就是要找到比q大的第一个数,我是用线段树维护的,找比q大的第一个数的位置;找到了之后你就会希望能得到以这个数为起点的最大可取数量,那么我们就要计算出每个位置开始可以得到的最大数量;实现方法就是倒过来遍历那n个数,每次查询i~n里比a[i]大的第一个数的位置cnt,然后假设记录数组为dp,那么就是dp[i]=dp[cnt]+1;没找到的情况就

相关推荐

推荐阅读