分类: 日记

  • Blazor Service 身份验证

    Blazor 是微软的新型的web开发方案,用来做全栈开发真是太爽了。

    之前用 Blazor 做了几个小应用,但是一直没有去做身份验证系统,之前也没有ASP.NET的基础,C#也是最近学的,而且重要的是,网上居然几乎没有相关的资料,于是咱就结合网上的一些信息和微软的官方文档(微软的文档是我见过最好的文档了)做了一个身份鉴权的小Demo。

    首先创建 Blazor Service 项目:

    然后在项目的目录下创建Auth目录存放鉴权的基础设施

    Provider 是鉴权的服务提供者

    UserAccount 是用户账号模型

    UserAccountService 是用户账号服务

    UserSession 是用户 session 数据模型

    在 Provider 下写自定义的基础认证服务,这里使用的session的形式做鉴权认证。

    using Microsoft.AspNetCore.Components.Authorization;
    using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage;
    using System.Security.Claims;
    
    namespace BSAuth.Auth
    {
        public class Provider : AuthenticationStateProvider
        {
            private readonly ProtectedSessionStorage _sessionStorage;
            private readonly ClaimsPrincipal _anonymous = new ClaimsPrincipal(new ClaimsIdentity());
            public Provider(ProtectedSessionStorage sessionStorage)
            {
                _sessionStorage = sessionStorage;
            }
    
            public override async Task<AuthenticationState> GetAuthenticationStateAsync()
            {
                try
                {
                    var userSessionStorageResult = await _sessionStorage.GetAsync<UserSession>("UserSession");
                    var userSession = userSessionStorageResult.Success ? userSessionStorageResult.Value : null;
                    if (userSession == null)
                    {
                        return await Task.FromResult(new AuthenticationState(_anonymous));
                    }
                    var claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity(new List<Claim>
                    {
                        new Claim(ClaimTypes.Name,userSession.UserName),
                        new Claim(ClaimTypes.Role,userSession.Role)
                    }, "customAuth")); ;
                    return await Task.FromResult(new AuthenticationState(claimsPrincipal));
                }
                catch
                {
                    return await Task.FromResult(new AuthenticationState(_anonymous));
                }
    
            }
            public async Task UpdateAuthState(UserSession userSession)
            {
                ClaimsPrincipal claimsPrincipal;
                if (userSession is not null)
                {
                    await _sessionStorage.SetAsync("UserSession", userSession);
                    claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity(new List<Claim>
                    {
                        new Claim(ClaimTypes.Name,userSession.UserName),
                        new Claim(ClaimTypes.Role,userSession.Role)
                    }));
                }
                else
                {
                    await _sessionStorage.DeleteAsync("UserSession");
                    claimsPrincipal = _anonymous;
                }
                NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(claimsPrincipal)));
            }
        }
    }
    

    创建 UserSession 模型和 UserAccount 模型

    namespace BSAuth.Auth
    {
        public class UserSession
        {
            public string UserName { get; set;}
            public string Role { get; set;}
        }
    }
    
    namespace BSAuth.Auth
    {
        public class UserAccount
        {
            public string UserName { get; set; }
            public string Password { get; set; }
            public string Role { get; set; }
        }
    }
    

    然后建立账户系统服务,这里Demo用的内存数据模拟,实际应该使用数据库

    namespace BSAuth.Auth
    {
        public class UserAccountService
        {
            private List<UserAccount> _user;
            public UserAccountService()
            {
                _user = new List<UserAccount>
                {
                    new UserAccount{UserName="张三",Password="zs",Role="admin"},
                    new UserAccount{UserName="李四",Password="ls",Role="user"}
                };
            }
            public UserAccount? GetByUserName(string userName)
            {
                return _user.FirstOrDefault(x => x.UserName == userName);
            }
        }
    }
    

    然后鉴权服务就做好啦,下面要将这些服务注入到项目中。

    在Program里依赖注入的形式注入基础认证服务和账号服务

    接下来在App.razor下使用认证视图标签

    一个简单的登录页面示例;

    @page "/login"
    @inject IJSRuntime ij
    @using BSAuth.Auth
    @inject UserAccountService userAccountService
    @inject AuthenticationStateProvider authStateProvider
    @inject NavigationManager navigation
    <h3>Login</h3>
    <input @bind="UserName" type="text"/>
    <input @bind="PassWord" type="password"/>
    <button @onclick="LoginAuth">登录</button>
    @code {
        string UserName { get; set; }
        string PassWord{ get; set; }
        private async Task LoginAuth()
        {
            var userAccount = userAccountService.GetByUserName(UserName);
            if(userAccount is  null || userAccount.Password != PassWord)
            {
                await ij.InvokeVoidAsync("alert", "登录失败");
                return;
            }
            var provider = (Provider)authStateProvider;
            await provider.UpdateAuthState(new UserSession
                {
                    UserName = userAccount.UserName,
                    Role = userAccount.Role
                });
            await ij.InvokeVoidAsync("alert", "登录成功");
            navigation.NavigateTo("/");
        }
    }
    

    然后可以在需要鉴权的视图资源下使用 AuthorizeView 组件

    具体使用方式请参照微软文档:ASP.NET Core Blazor 身份验证和授权 | Microsoft Learn

  • 大模拟,构建json字符串

    在做学校的练习题的时候遇见的题目,感觉挺有意思的,一开始想的是使用结构体,构建json节点,结构体中包含string类型的key和object类型的value,因为json记录的值可能是数组,可能是对象,可能是字符串或者是数字,但是在C#中都是object的派生类,所以创建一个object类的value即可。但是这样构建出来的json最后输出的时候还是要进行格式化的输出,相当于一开始输入格式化成json再把json的结构体格式化输出成字符串,复杂了一些,于是想到直接用栈储存json中的括号,每次返回上一级的时候出栈,栈空即结束。这样题目就像是一个简单的分支判断题目。

    题目:

    出题人一时兴起,来了个大模拟
    题目要求根据输入,构建一段json字符串
    json中含以下几种元素

    • 对象
    • 数组
    • 记录(我自己这样叫它)

    记录,是一种"名字":值形式的字符串
    如下面格式即记录

    "ett":"1593050051366"
    

    值不仅仅可以是一个字符串,也可以是一个对象或数组

    "cm":{
            "ln":"-55.0",
            "sv":"V2.9.6",
            "os":"8.0.4",
            "g":"C6816QZ0@gmail.com",
            "mid":"489",
            "nw":"3G",
            "l":"es",
            "vc":"4",
            "hw":"640*960",
            "ar":"MX",
            "uid":"489",
            "t":"1593123253541",
            "la":"5.2",
            "md":"sumsung-18",
            "vn":"1.3.4",
            "ba":"Sumsung",
            "sr":"I"
        }
    

    (数据来自百度截取,干啥的我也不知道)

     "你" : [18874,15157]
    

    对象,即使用大括号{}括起来的多个(或单个甚至无)记录组成,多个记录之间用逗号,隔开

    {
            "ln":"-55.0",
            "sv":"V2.9.6",
            "os":"8.0.4",
            "g":"C6816QZ0@gmail.com",
            "mid":"489",
            "nw":"3G",
            "l":"es",
            "vc":"4",
            "hw":"640*960",
            "ar":"MX",
            "uid":"489",
            "t":"1593123253541",
            "la":"5.2",
            "md":"sumsung-18",
            "vn":"1.3.4",
            "ba":"Sumsung",
            "sr":"I"
        }
    

    这就是一个对象


    数组是有多个值组成,包含在方括号[]中,同样以逗号,隔开,如

    ["aaa","bbb","ccc"]
    

    json格式非常自由,可以不使用换行符和空格,只要符合上述格式,它就是合法的json
    同时,json数据的值可以是文本,也可以是整数布尔型等

    在此题目做出以下简化:

    • 输出格式不包含任何空格和换行
    • 值仅包含文本型,即值仅有用引号""包含的一种格式

      输入格式:

    输入包含多行,规则如下

    • 1 name vaule 插入一个记录,名字为name,记录值为vaule
    • 2 name 插入一个值为数组的记录,并在之后输入进入数组的输入(进入下一级记录)
    • 3 name 插入一个值为对象的记录,名字为name,并在之后的输入进入对象的输入(进入下一级记录)
    • 0本级输入完毕,退回到上级输入
      最上级输入完毕后,即输入结束

    关于数组的输入:
    数组每个元素可以看作没有名字的记录,因此,数组的输入格式如下

    • 1 vaule 插入一个记录,记录值为vaule
    • 2 插入一个值为数组的记录,并在之后输入进入数组的输入(进入下一级记录)
    • 3 插入一个值为对象的记录,并在之后的输入进入对象的输入(进入下一级记录)
    • 0数组输入完毕

    输出格式:

    输出一段不包含换行和空格的json数据

    输入样例:

    1 a 1
    1 b 12345
    2 c
    1 1
    1 2
    1 3
    1 4
    1 5
    0
    2 d
    3
    1 a 1
    1 b 2
    0
    3
    1 c 4
    1 d 5
    0
    0
    0
    

    输出样例:

    {"a":"1","b":"12345","c":["1","2","3","4","5"],"d":[{"a":"1","b":"2"},{"c":"4","d":"5"}]}
    

    输入解释

    1 a 1 插入记录"a":"1"
    1 b 12345 插入记录"b":"12345"
    2 c 插入一个数组
    1 1 插入记录"1"
    1 2 插入记录"2"
    1 3 插入记录"3"
    1 4 插入记录"4"
    1 5 插入记录"5"
    0 数组输入完毕
    2 d 插入数组
    3 插入一个对象
    1 a 1 对象第一项为“a”:"1"
    1 b 2对象第二项为“b”:"2"
    0 对象输入完毕
    3
    1 c 4
    1 d 5
    0 (以上4行同上)
    0 数组输入完毕
    0 对象(最顶层的)输入完毕
    

    题目说明

    • 输入保证所以输入字符串长度小于100
    • 最外层也为对象,因此最外层有{}包括
    • 输出顺序与输入相同
    • 出题人用c实现的,你好意思拿python吗(
    • 如果有问题,请优先群内问,这样你的问题其他同学也可以看见,其他同学的问题,你也可以看见,同时有多个同学可以来一起帮助你,效率非常高。
    • 私聊在解决问题方面,效率很低,如果你想尽快的解决问题,请优先在群里问。
    using System;
    using System.Collections.Generic;
    using System.Text;
    
    // ReSharper disable once CheckNamespace
    
    public static class Program
    {
        private static readonly Stack<char> Stack = new Stack<char>();
        public static void Main()
        {
    
            Stack.Push('}');
            var ans = new StringBuilder();
            ans.Append('{');
            var flag = 0;
            while (Stack.Count != 0)
            {
                var input = Console.ReadLine()?.Trim().Split();
                switch (input?[0])
                {
                    case "1":
                        if (input.Length == 3)
                        {
                            ans.Append($"\"{input[1]}\":\"{input[2]}\",");
                        }
                        else
                        {
                            ans.Append($"\"{input[1]}\",");
                        }
                        break;
                    case "2":
                        if (input.Length == 2)
                        {
                            Stack.Push(']');
                            ans.Append($"\"{input[1]}\":[");
                        }
                        else
                        {
                            flag = 1;
                        }
                        break;
                    case "3":
                        if (input.Length == 1)
                        {
                            Stack.Push('}');
                            ans.Append("{");
                        }
                        else
                        {
                            ans.Append($"\"{input[1]}\":"+"{");
                            Stack.Push('}');
                        }
                        break;
                    case "0":
                        if (ans[ans.Length - 1] == ',')
                        {
                            ans.Remove(ans.Length - 1, 1);
                        }
                        ans.Append(Stack.Pop());
                        ans.Append(',');
                        break;
                    default:
                        switch (flag)
                        {
                            case 1:
                                Stack.Push(']');
                                ans.Append($"\"{input?[0]}\":[");
                                break;
                        }
                        break;
                }
            }
            ans.Remove(ans.Length - 1, 1);
            Console.WriteLine(ans);
    
        }
    }
    
  • 爬虫

    这是接了一个爬虫单子给别人做的几个小爬虫作业,requests爬取,正则匹配,xls储存和数据库储存,都是比较简单的例子,所以就开源啦~

    下载:爬虫.zip – 蓝奏云 (lanzoul.com)

    截图:

  • PTA的效率,呜呜太强啦

    本着试一试的心态给pta发送了建议邮件,因为pta的C#判题编译器的版本比较低,想支持dotnet 6,在上午十点四十六的时候向官方发了这封邮件,本来以为应该会很长时间才会回复,或者直接不回复,没想到半个小时就收到了官方的回复~

  • 从坦克大战中学到什么

    90坦克大战是非常经典的小游戏,甚至当时的几乎所有游戏机都带有这个游戏。在学C#的时候就想不如那这个小游戏来熟悉一下C#语法特性,于是就做出来了这个可以2P对战的坦克大战。

    做的过程中感觉和写Java区别不大,但是有很多特性可以更简单的写,写的过程中也熟悉了更多面向对象的方法,因为之前几乎一直在写python和js这俩的面向对象不是很经典,甚至类是阉割的,在用C#的过程中对虚方法,抽象类等概念有了更深的理解。

    特别在派生类方面,考虑不同实体的关系,做出游戏对象的基类,移动物体类,不可移动物体类,继承派生出坦克,子弹,墙体等等