【ASP.NET Core分布式项目实战】(三)整理IdentityServer4 MVC授权、Consent功能实现
原文:【ASP.NET Core分布式项目实战】(三)整理IdentityServer4 MVC授权、Consent功能实现

本博客根据http://video.jessetalk.cn/my/course/5视频整理(内容可能会有部分,推荐看源视频学习)

前言

由于之前的博客都是基于其他的博客进行开发,现在重新整理一下方便以后后期使用与学习

新建IdentityServer4服务端

服务端也就是提供服务,如QQ Weibo等。

新建项目解决方案AuthSample.

新建一个ASP.NET Core Web Application 项目MvcCookieAuthSample,选择模板Web 应用程序 不进行身份验证。

给网站设置默认地址     http://localhost:5000

第一步:添加Nuget包:IdentityServer4

添加IdentityServer4 引用:

Install-Package IdentityServer4

第二步:添加Config.cs配置类

然后添加配置类Config.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using IdentityServer4;
using IdentityServer4.Models;
using IdentityServer4.Test;namespace MvcCookieAuthSample
{public class Config{//所有可以访问的Resourcepublic static IEnumerable<ApiResource> GetApiResources(){return new List<ApiResource>(){new ApiResource("api1","API Application")};}//客户端public static IEnumerable<Client> GetClients(){return new List<Client>{new Client{ClientId="mvc",AllowedGrantTypes=GrantTypes.Implicit,//模式:隐式模式ClientSecrets={//私钥new Secret("secret".Sha256())},AllowedScopes={//运行访问的资源
                        IdentityServerConstants.StandardScopes.Profile,IdentityServerConstants.StandardScopes.OpenId,},RedirectUris={"http://localhost:5001/signin-oidc"},//跳转登录到的客户端的地址PostLogoutRedirectUris={"http://localhost:5001/signout-callback-oidc"},//跳转登出到的客户端的地址RequireConsent=false//是否需要用户点击确认进行跳转
                }};}//测试用户public static List<TestUser> GetTestUsers(){return new List<TestUser>{new TestUser{SubjectId="10000",Username="wyt",Password="password"}};}//定义系统中的资源public static IEnumerable<IdentityResource> GetIdentityResources(){return new List<IdentityResource>{//这里实际是claims的返回资源new IdentityResources.OpenId(),new IdentityResources.Profile(),new IdentityResources.Email()};}}
}

View Code

第三步:添加Startup配置

引用命名空间:

using IdentityServer4;

然后打开Startup.cs 加入如下:

services.AddIdentityServer().AddDeveloperSigningCredential()//添加开发人员签名凭据.AddInMemoryApiResources(Config.GetApiResources())//添加内存apiresource.AddInMemoryClients(Config.GetClients())//添加内存client.AddInMemoryIdentityResources(Config.GetIdentityResources())//添加系统中的资源.AddTestUsers(Config.GetTestUsers());//添加测试用户

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{...app.UseIdentityServer();...
}

注册登录实现

我们还需要新建一个ViewModels,在ViewModels中新建RegisterViewModel.cs和LoginViewModel.cs来接收表单提交的值以及来进行强类型视图

using System.ComponentModel.DataAnnotations;namespace MvcCookieAuthSample.ViewModels
{public class RegisterViewModel{[Required]//必须的[DataType(DataType.EmailAddress)]//内容检查是否为邮箱public string Email { get; set; }[Required]//必须的[DataType(DataType.Password)]//内容检查是否为密码public string Password { get; set; }[Required]//必须的[DataType(DataType.Password)]//内容检查是否为密码public string ConfirmedPassword { get; set; }}
}

View Code

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;namespace MvcCookieAuthSample.ViewModels
{public class LoginViewModel{[Required]public string UserName { get; set; }[Required]//必须的[DataType(DataType.Password)]//内容检查是否为密码public string Password { get; set; }}
}

View Code

在Controllers文件夹下新建AdminController.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;namespace MvcCookieAuthSample.Controllers
{public class AdminController : Controller{public IActionResult Index(){return View();}}
}

View Code

在Views文件夹下新建Admin文件夹,并在Admin文件夹下新建Index.cshtml

@{ViewData["Title"] = "Admin";
}
<h2>@ViewData["Title"]</h2><p>Admin Page</p>

View Code

在Controllers文件夹下新建AccountController.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using IdentityServer4.Test;
using Microsoft.AspNetCore.Identity;
using MvcCookieAuthSample.ViewModels;
using Microsoft.AspNetCore.Authentication;namespace MvcCookieAuthSample.Controllers
{public class AccountController : Controller{private readonly TestUserStore _users;public AccountController(TestUserStore users){_users = users;}//内部跳转private IActionResult RedirectToLocal(string returnUrl){if (Url.IsLocalUrl(returnUrl)){//如果是本地return Redirect(returnUrl);}return RedirectToAction(nameof(HomeController.Index), "Home");}//添加验证错误private void AddError(IdentityResult result){//遍历所有的验证错误foreach (var error in result.Errors){//返回error到modelModelState.AddModelError(string.Empty, error.Description);}}public IActionResult Register(string returnUrl = null){ViewData["returnUrl"] = returnUrl;return View();}[HttpPost]public async Task<IActionResult> Register(RegisterViewModel registerViewModel, string returnUrl = null){return View();}public IActionResult Login(string returnUrl = null){ViewData["returnUrl"] = returnUrl;return View();}[HttpPost]public async Task<IActionResult> Login(LoginViewModel loginViewModel, string returnUrl = null){if (ModelState.IsValid){ViewData["returnUrl"] = returnUrl;var user = _users.FindByUsername(loginViewModel.UserName);if (user==null){ModelState.AddModelError(nameof(loginViewModel.UserName), "UserName not exists");}else{if (_users.ValidateCredentials(loginViewModel.UserName,loginViewModel.Password)){//是否记住var prop = new AuthenticationProperties{IsPersistent = true,ExpiresUtc = DateTimeOffset.UtcNow.Add(TimeSpan.FromMinutes(30))};await Microsoft.AspNetCore.Http.AuthenticationManagerExtensions.SignInAsync(HttpContext, user.SubjectId, user.Username, prop);}}return RedirectToLocal(returnUrl);}return View();}public async Task<IActionResult> Logout(){await HttpContext.SignOutAsync();return RedirectToAction("Index", "Home");}}
}

View Code

然后在Views文件夹下新增Account文件夹并新增Register.cshtml与Login.cshtml视图

@{ViewData["Title"] = "Register";
}@using MvcCookieAuthSample.ViewModels;
@model RegisterViewModel;<h2>@ViewData["Title"]</h2>
<h3>@ViewData["Message"]</h3><div class="row"><div class="col-md-4">@* 这里将asp-route-returnUrl="@ViewData["returnUrl"],就可以在进行register的post请求的时候接收到returnUrl *@<form method="post" asp-route-returnUrl="@ViewData["returnUrl"]"><h4>Create a new account.</h4><hr />@*统一显示错误信息*@<div class="text-danger" asp-validation-summary="All"></div><div class="form-group"><label asp-for="Email"></label><input asp-for="Email" class="form-control" /><span asp-validation-for="Email" class="text-danger"></span></div><div class="form-group"><label asp-for="Password"></label><input asp-for="Password" class="form-control" /><span asp-validation-for="Password" class="text-danger"></span></div><div class="form-group"><label asp-for="ConfirmedPassword"></label><input asp-for="ConfirmedPassword" class="form-control" /><span asp-validation-for="ConfirmedPassword" class="text-danger"></span></div><button type="submit" class="btn btn-default">Register</button></form></div>
</div>

View Code

@{ViewData["Title"] = "Login";
}@using MvcCookieAuthSample.ViewModels;
@model LoginViewModel;<div class="row"><div class="col-md-4"><section><form method="post" asp-controller="Account" asp-action="Login"  asp-route-returnUrl="@ViewData["returnUrl"]"><h4>Use a local account to log in.</h4><hr />@*统一显示错误信息*@<div class="text-danger" asp-validation-summary="All"></div><div class="form-group"><label asp-for="UserName"></label><input asp-for="UserName" class="form-control" /><span asp-validation-for="UserName" class="text-danger"></span></div><div class="form-group"><label asp-for="Password"></label><input asp-for="Password" type="password" class="form-control" /><span asp-validation-for="Password" class="text-danger"></span></div><div class="form-group"><button type="submit" class="btn btn-default">Log in</button></div></form></section></div>
</div>@section Scripts{@await Html.PartialAsync("_ValidationScriptsPartial")
}

View Code

我们接下来要修改_Layout.cshtml视图页面判断注册/登陆按钮是否应该隐藏

完整的_Layout.cshtml代码:

<!DOCTYPE html>
<html>
<head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>@ViewData["Title"] - MvcCookieAuthSample</title><environment include="Development"><link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" /><link rel="stylesheet" href="~/css/site.css" /></environment><environment exclude="Development"><link rel="stylesheet" href="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/css/bootstrap.min.css"asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css"asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-value="absolute" /><link rel="stylesheet" href="~/css/site.min.css" asp-append-version="true" /></environment>
</head>
<body><nav class="navbar navbar-inverse navbar-fixed-top"><div class="container"><div class="navbar-header"><button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse"><span class="sr-only">Toggle navigation</span><span class="icon-bar"></span><span class="icon-bar"></span><span class="icon-bar"></span></button><a asp-area="" asp-controller="Home" asp-action="Index" class="navbar-brand">MvcCookieAuthSample</a></div><div class="navbar-collapse collapse"><ul class="nav navbar-nav"><li><a asp-area="" asp-controller="Home" asp-action="Index">Home</a></li><li><a asp-area="" asp-controller="Home" asp-action="About">About</a></li><li><a asp-area="" asp-controller="Home" asp-action="Contact">Contact</a></li></ul>@if (User.Identity.IsAuthenticated){<form asp-action="Logout" asp-controller="Account" method="post"><ul class="nav navbar-nav navbar-right"><li><a title="Welcome" asp-controller="Admin" asp-action="Index">@User.Identity.Name</a></li><li><button type="submit" class="btn btn-link navbar-btn navbar-link">Log out</button></li></ul></form>}else{<ul class="nav navbar-nav navbar-right"><li><a asp-area="" asp-controller="Account" asp-action="Register">Register</a></li><li><a asp-area="" asp-controller="Account" asp-action="Login">Log in</a></li></ul>}</div></div></nav><div class="container body-content">@RenderBody()<hr /><footer><p>&copy; 2018 - MvcCookieAuthSample</p></footer></div><environment include="Development"><script src="~/lib/jquery/dist/jquery.js"></script><script src="~/lib/bootstrap/dist/js/bootstrap.js"></script><script src="~/js/site.js" asp-append-version="true"></script></environment><environment exclude="Development"><script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-2.2.0.min.js"asp-fallback-src="~/lib/jquery/dist/jquery.min.js"asp-fallback-test="window.jQuery"crossorigin="anonymous"integrity="sha384-K+ctZQ+LL8q6tP7I94W+qzQsfRV2a+AfHIi9k8z8l9ggpc8X+Ytst4yBo/hH+8Fk"></script><script src="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/bootstrap.min.js"asp-fallback-src="~/lib/bootstrap/dist/js/bootstrap.min.js"asp-fallback-test="window.jQuery && window.jQuery.fn && window.jQuery.fn.modal"crossorigin="anonymous"integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa"></script><script src="~/js/site.min.js" asp-append-version="true"></script></environment>@RenderSection("Scripts", required: false)
</body>
</html>

View Code

最后给AdminController加上  [Authorize]  特性标签即可  

然后我们就可以运行网站,输入用户名和密码进行登录了

新建客户端

新建一个MVC网站MvcClient

dotnet new mvc --name MvcClient

给网站设置默认地址     http://localhost:5001

MVC的网站已经内置帮我们实现了Identity,所以我们不需要再额外添加Identity引用

添加认证

services.AddAuthentication(options =>
{options.DefaultScheme = "Cookies";//使用Cookies认证options.DefaultChallengeScheme = "oidc";//使用oidc
})
.AddCookie("Cookies")//配置Cookies认证
.AddOpenIdConnect("oidc",options=> {//配置oidcoptions.SignInScheme = "Cookies";options.Authority = "http://localhost:5000";options.RequireHttpsMetadata = false;options.ClientId = "mvc";options.ClientSecret = "secret";options.SaveTokens = true;
});

在管道中使用Authentication

app.UseAuthentication();

接下来我们在HomeController上打上  [Authorize]  标签,然后启动运行

我们这个时候访问首页http://localhost:5001会自动跳转到ocalhost:5000/account/login登录

登录之后会自动跳转回来

我们可以在Home/About页面将claim的信息显示出来

@{ViewData["Title"] = "About";
}
<h2>@ViewData["Title"]</h2>
<h3>@ViewData["Message"]</h3><dl>@foreach (var claim in User.Claims){<dt>@claim.Type</dt><dt>@claim.Value</dt>}
</dl>

View Code

这边的内容是根据我们在IdentityServer服务中定义的返回资源决定的

Consent功能实现

首先在ViewModels文件夹下创建两个视图模型

ScopeViewModel.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;namespace MvcCookieAuthSample.ViewModels
{//领域public class ScopeViewModel{public string Name { get; set; }public string DisplayName { get; set; }public string Description { get; set; }public bool Emphasize { get; set; }public bool Required { get; set; }public bool Checked { get; set; }}
}

View Code

ConsentViewModel.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;namespace MvcCookieAuthSample.ViewModels
{public class ConsentViewModel{public string ClientId { get; set; }public string ClientName { get; set; }public string ClientUrl { get; set; }public string ClientLogoUrl { get; set; }public bool AllowRememberConsent { get; set; }public IEnumerable<ScopeViewModel> IdentityScopes { get; set; }public IEnumerable<ScopeViewModel> ResourceScopes { get; set; }}
}

View Code

我们在MvcCookieAuthSample项目中添加新控制器ConsentController

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using MvcCookieAuthSample.ViewModels;
using IdentityServer4.Models;
using IdentityServer4.Services;
using IdentityServer4.Stores;namespace MvcCookieAuthSample.Controllers
{public class ConsentController : Controller{private readonly IClientStore _clientStore;private readonly IResourceStore _resourceStore;private readonly IIdentityServerInteractionService _identityServerInteractionService;public ConsentController(IClientStore clientStore, IResourceStore resourceStore, IIdentityServerInteractionService identityServerInteractionService){_clientStore = clientStore;_resourceStore = resourceStore;_identityServerInteractionService = identityServerInteractionService;}private async Task<ConsentViewModel> BuildConsentViewModel(string returnUrl){var request =await _identityServerInteractionService.GetAuthorizationContextAsync(returnUrl);if (request == null)return null;var client =await  _clientStore.FindEnabledClientByIdAsync(request.ClientId);var resources =await _resourceStore.FindEnabledResourcesByScopeAsync(request.ScopesRequested);return CreateConsentViewModel(request, client, resources);}private ConsentViewModel CreateConsentViewModel(AuthorizationRequest request,Client client,Resources resources){var vm = new ConsentViewModel();vm.ClientName = client.ClientName;vm.ClientLogoUrl = client.LogoUri;vm.ClientUrl = client.ClientUri;vm.AllowRememberConsent = client.AllowRememberConsent;vm.IdentityScopes = resources.IdentityResources.Select(i => CreateScopeViewModel(i));vm.ResourceScopes = resources.ApiResources.SelectMany(i =>i.Scopes).Select(i=>CreateScopeViewModel(i));return vm;}private ScopeViewModel CreateScopeViewModel(IdentityResource identityResource){return new ScopeViewModel{Name = identityResource.Name,DisplayName = identityResource.DisplayName,Description = identityResource.Description,Required = identityResource.Required,Checked = identityResource.Required,Emphasize = identityResource.Emphasize};}private ScopeViewModel CreateScopeViewModel(Scope scope){return new ScopeViewModel{Name = scope.Name,DisplayName = scope.DisplayName,Description = scope.Description,Required = scope.Required,Checked = scope.Required,Emphasize = scope.Emphasize};}[HttpGet]public async Task<IActionResult> Index(string returnUrl){var model =await BuildConsentViewModel(returnUrl);if (model==null){}return View(model);}}
}

View Code

然后新建Idenx.cshtml视图和_ScopeListitem.cshtml分部视图

_ScopeListitem.cshtml

@using MvcCookieAuthSample.ViewModels;
@model ScopeViewModel<li><label><input type="checkbox" name="ScopesConsented" id="scopes_@Model.Name" value="@Model.Name" checked="@Model.Checked" disabled="@Model.Required"/><strong>@Model.Name</strong>@if (Model.Emphasize){<span class="glyphicon glyphicon-exclamation-sign"></span>}</label>@if (string.IsNullOrWhiteSpace(Model.Description)){<div><label for="scopes_@Model.Name">@Model.Description</label></div>}
</li>

View Code

Idenx.cshtml

@using MvcCookieAuthSample.ViewModels;
@model ConsentViewModel
<p>Consent Page</p>
<!--Client Info-->
<div class="row page-header"><div class="col-sm-10">@if (!string.IsNullOrWhiteSpace(Model.ClientLogoUrl)){<div><img src="@Model.ClientLogoUrl" /></div>}<h1>@Model.ClientName<small>希望使用你的账户</small></h1></div>
</div><!--Scope Info-->
<div class="row"><div class="col-sm-8"><form asp-action="Index">@if (Model.IdentityScopes.Any()){<div><div class="panel-heading"><span class="glyphicon glyphicon-user"></span>用户信息</div><ul class="list-group">@foreach (var scope in Model.IdentityScopes){@Html.Partial("_ScopeListitem",scope)}</ul></div>}@if (Model.ResourceScopes.Any()){<div><div class="panel-heading"><span class="glyphicon glyphicon-tasks"></span>应用权限</div><ul class="list-group">@foreach (var scope in Model.ResourceScopes){@Html.Partial("_ScopeListitem",scope)}</ul></div>}</form></div>
</div>

View Code

最后我们修改Config.cs,增加一些信息

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using IdentityServer4;
using IdentityServer4.Models;
using IdentityServer4.Test;namespace MvcCookieAuthSample
{public class Config{//所有可以访问的Resourcepublic static IEnumerable<ApiResource> GetApiResources(){return new List<ApiResource>(){new ApiResource("api1","API Application")};}//客户端public static IEnumerable<Client> GetClients(){return new List<Client>{new Client{ClientId="mvc",AllowedGrantTypes=GrantTypes.Implicit,//模式:隐式模式ClientSecrets={//私钥new Secret("secret".Sha256())},AllowedScopes={//运行访问的资源
                        IdentityServerConstants.StandardScopes.Profile,IdentityServerConstants.StandardScopes.OpenId,
                        IdentityServerConstants.StandardScopes.Email,},RedirectUris={"http://localhost:5001/signin-oidc"},//跳转登录到的客户端的地址PostLogoutRedirectUris={"http://localhost:5001/signout-callback-oidc"},//跳转登出到的客户端的地址RequireConsent=true,//是否需要用户点击确认进行跳转,改为点击确认后进行跳转ClientName="MVC Clent",ClientUri="http://localhost:5001",LogoUri="https://chocolatey.org/content/packageimages/aspnetcore-runtimepackagestore.2.0.0.png",AllowRememberConsent=true,}};}//测试用户public static List<TestUser> GetTestUsers(){return new List<TestUser>{new TestUser{SubjectId="10000",Username="wyt",Password="password",}};}//定义系统中的资源public static IEnumerable<IdentityResource> GetIdentityResources(){return new List<IdentityResource>{//这里实际是claims的返回资源new IdentityResources.OpenId(),new IdentityResources.Profile(),new IdentityResources.Email()};}}
}

我们这个时候访问首页http://localhost:5001会自动跳转到ocalhost:5000/account/login登录

登录之后会自动跳转到登录确认页面

posted on 2019-03-29 00:27 NET未来之路 阅读(...) 评论(...) 编辑 收藏

转载于:https://www.cnblogs.com/lonelyxmas/p/10618795.html

【ASP.NET Core分布式项目实战】(三)整理IdentityServer4 MVC授权、Consent功能实现...相关推荐

  1. ASP.NET Core分布式项目实战(详解oauth2授权码流程)--学习笔记

    最近公司产品上线,通宵加班了一个月,一直没有更新,今天开始恢复,每日一更,冲冲冲 任务13:详解oauth2授权码流程 我们即将开发的产品有一个用户 API,一个项目服务 API,每个服务都需要认证授 ...

  2. ASP.NET Core分布式项目实战(集成ASP.NETCore Identity)--学习笔记

    任务24:集成ASP.NETCore Identity 之前在 Index 页面写了一个 strong 标签,需要加个判断再显示,不然为空没有错误的时候也会显示 @if (!ViewContext.M ...

  3. ASP.NET Core分布式项目实战(Consent 确认逻辑实现)--学习笔记

    任务22:Consent 确认逻辑实现 接下来,我们会在上一节的基础上添加两个按钮,同意和不同意,点击之后会把请求 post 到 ConsentController 处理,如果同意会通过 return ...

  4. ASP.NET Core分布式项目实战(运行Consent Page)--学习笔记

    任务21:运行Consent Page 修改 Config.cs 中的 RequireConsent 为 true,这样登录的时候就会跳转到 Consent 页面 修改 ConsentControll ...

  5. ASP.NET Core分布式项目实战(Consent Controller Get请求逻辑实现)--学习笔记

    任务20:Consent Controller Get请求逻辑实现 接着上一节的思路,实现一下 ConsentController 根据流程图在构造函数注入 IClientStore,IResourc ...

  6. ASP.NET Core分布式项目实战(oauth2 + oidc 实现 client部分)--学习笔记

    任务16:oauth2 + oidc 实现 client部分 实现 client 之前启动一下上一节的 server,启动之前需要清除一些代码 注释 Program 的 MigrateDbContex ...

  7. ASP.NET Core分布式项目实战(oauth2 + oidc 实现 server部分)--学习笔记

    任务15:oauth2 + oidc 实现 server部分 基于之前快速入门的项目(MvcCookieAuthSample): ASP.NET Core快速入门(第5章:认证与授权)--学习笔记 A ...

  8. ASP.NET Core分布式项目实战(客户端集成IdentityServer)--学习笔记

    任务9:客户端集成IdentityServer 新建 API 项目 dotnet new webapi --name ClientCredentialApi 控制器添加验证 using Microso ...

  9. ASP.NET Core分布式项目实战(第三方ClientCredential模式调用)--学习笔记

    任务10:第三方ClientCredential模式调用 创建一个控制台程序 dotnet new console --name ThirdPartyDemo 添加 Nuget 包:IdentityM ...

  10. ASP.NET Core分布式项目实战(业务介绍,架构设计,oAuth2,IdentityServer4)--学习笔记...

    任务4:第一章计划与目录 敏捷产品开发流程 原型预览与业务介绍 整体架构设计 API 接口设计 / swagger Identity Server 4 搭建登录 账号 API 实现 配置中心 任务5: ...

最新文章

  1. 使用java+TestNG进行接口回归测试 1
  2. 计算机信息安全专业留学,2021美国信息安全专业排名Top50大学!
  3. golang gorm 基本使用
  4. datagrid显示mysql_WPF DataGrid显示MySQL查询信息,且可删除、修改、插入 (原发布 csdn 2018-10-13 20:07:28)...
  5. apache htpasswd.exe创建密码
  6. 1.5 编程基础之循环控制 36 计算多项式的值 python
  7. 【Pytorch】expand()用法==》扩展某个维度
  8. @excel 注解_Java中注解学习系列教程-3
  9. JVM学习之GC参数设置
  10. elasticsearch2.x优化小结(单节点)
  11. java list 排序_java 对list进行排序
  12. java读取摄像头视屏流,Java 摄像头视频获取
  13. 探讨破解3G今日困局之策
  14. html怎样使图片自动旋转,css怎么让图片旋转?
  15. 年终总结,怎么写领导才满意?
  16. Android11.0(R) MTK 预置可卸载app恢复出厂不恢复(仿RK方案)
  17. js-面向对象的程序设计,函数表达式
  18. Android 内核源码编译记录
  19. Linux/Windows快速镜像安装包下载
  20. 5G无线定位技术标准化及发展趋势

热门文章

  1. 【pytest官方文档】解读-fixtures函数和测试函数的参数化
  2. 【pytest】之parameterize()参数化,实现测试方法数据化
  3. 通过U盘安装windows简易教程
  4. macbook pro怎么养小宠物?macbook pro养宠物设置方法
  5. vue项目中如何简单的读取声音文件
  6. 中考计算机考试辽宁,2019年辽宁中考考试时间安排,辽宁中考考试科目时间安排表...
  7. matlab相机标定工具箱讲解,matlab 相机标定工具箱
  8. Three.js和其它webgl框架
  9. Redis - 一个简单的抢红包小项目
  10. 线程池函数1 - 异步调用函数