2020/01/31, ASP.NET Core 3.1, VS2019, Microsoft.AspNetCore.Authentication.JwtBearer 3.1.1

摘要:基于ASP.NET Core 3.1 WebApi搭建后端多层网站架构【10-使用JWT进行授权验证】

使用JWT给网站做授权验证

文章目录

此分支项目代码

本章节介绍了使用JWT给网站做授权验证

添加包引用

MS.Component.Jwt类库中添加Microsoft.AspNetCore.Authentication.JwtBearer包引用:

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="3.1.1" />
</ItemGroup>

MS.Component.Jwt类库中引用MS.EntitiesMS.WebCore项目

MS.Models类库中确保已引用MS.Component.Jwt项目

添加jwt配置

appsettings.json

MS.WebApi应用程序的appsettings.json中增加JwtSetting节点:

"JwtSetting": {
"Issuer": "MS.WebHost",
"Audience": "MS.Audience",
"SecurityKey": "MS.WebHost SecurityKey", //more than 16 chars
"LifeTime": 1440 //(minutes) token life time default:1440 m=1 day
}
  • Issuer是颁发者
  • Audience是受众
  • SecurityKey是安全密钥,至少要16个字符
  • LifeTime是token的存活时间,这里指定了时间单位是分钟,注意JWT有自己默认的缓冲过期时间(五分钟)

JwtSetting.cs

MS.Component.Jwt类库中添加JwtSetting.cs类:

namespace MS.Component.Jwt
{
public class JwtSetting
{
/// <summary>
/// 颁发者
/// </summary>
public string Issuer { get; set; } /// <summary>
/// 受众
/// </summary>
public string Audience { get; set; } /// <summary>
/// 安全密钥
/// </summary>
public string SecurityKey { get; set; } /// <summary>
/// 过期时间
/// </summary>
public double LifeTime { get; set; }
}
}

可以使用选择性粘贴,将json直接粘贴为类

添加UserClaim

MS.Component.Jwt类库中新建UserClaim文件夹,在该文件夹中新建UserClaimType.csIClaimsAccessor.csClaimsAccessor.csUserData.cs类:

UserClaimType.cs

namespace MS.Component.Jwt.UserClaim
{
public static class UserClaimType
{
public const string Id = "http://schemas.microsoft.com/ws/2008/06/identity/claims/primarysid";
public const string Account = "http://schemas.microsoft.com/ws/2008/06/identity/claims/serialnumber";
public const string Name = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name";
public const string Email = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress";
public const string Phone = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/mobilephone";
public const string RoleName = "http://schemas.microsoft.com/ws/2008/06/identity/claims/role";
public const string RoleDisplayName = "http://schemas.xmlsoap.org/ws/2009/09/identity/claims/actor";
}
}

这个类是声明用户信息的

里面的值都是从System.Security.Claims.ClaimTypes里挑选出来的值,也可以自行定义

ClaimsAccessor.cs

IClaimsAccessor接口:

namespace MS.Component.Jwt.UserClaim
{
public interface IClaimsAccessor
{
string UserName { get; }
long UserId { get; }
string UserAccount { get; }
string UserRole { get; }
string UserRoleDisplayName { get; }
}
}

ClaimsAccessor实现:

using Microsoft.AspNetCore.Http;
using System;
using System.Linq;
using System.Security.Claims; namespace MS.Component.Jwt.UserClaim
{
public class ClaimsAccessor : IClaimsAccessor
{
private readonly IHttpContextAccessor _httpContextAccessor; public ClaimsAccessor(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
} public ClaimsPrincipal UserPrincipal
{
get
{
ClaimsPrincipal user = _httpContextAccessor.HttpContext.User;
if (user.Identity.IsAuthenticated)
{
return user;
}
else
{
throw new Exception("用户未认证");
}
}
}
public string UserName
{
get
{
return UserPrincipal.Claims.First(x => x.Type == UserClaimType.Name).Value;
}
}
public long UserId
{
get
{
return long.Parse(UserPrincipal.Claims.First(x => x.Type == UserClaimType.Id).Value);
} }
public string UserAccount
{
get
{
return UserPrincipal.Claims.First(x => x.Type == UserClaimType.Account).Value;
}
}
public string UserRole
{
get
{
return UserPrincipal.Claims.First(x => x.Type == UserClaimType.RoleName).Value;
}
}
public string UserRoleDisplayName
{
get
{
return UserPrincipal.Claims.First(x => x.Type == UserClaimType.RoleDisplayName).Value;
}
}
}
}

定义用户信息访问接口,开发时通过获取IClaimsAccessor接口来获取登录用户的信息。

UserData.cs

namespace MS.Component.Jwt.UserClaim
{
public class UserData
{
public long Id { get; set; }
public string Account { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public string Phone { get; set; }
public string RoleName { get; set; }
public string RoleDisplayName { get; set; } public string Token { get; set; }
}
}

定义用户数据类

jwt服务

MS.Component.Jwt类库中新建JwtService.cs类:

using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using MS.Component.Jwt.UserClaim;
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text; namespace MS.Component.Jwt
{
public class JwtService
{
private readonly JwtSetting _jwtSetting;
private readonly TimeSpan _tokenLifeTime; public JwtService(IOptions<JwtSetting> options)
{
_jwtSetting = options.Value;
_tokenLifeTime = TimeSpan.FromMinutes(options.Value.LifeTime);
}
/*
iss (issuer):签发人
exp (expiration time):过期时间
sub (subject):主题
aud (audience):受众
nbf (Not Before):生效时间
iat (Issued At):签发时间
jti (JWT ID):编号
*/ /// <summary>
/// 生成身份信息
/// </summary>
/// <param name="userName">用户名</param>
/// <param name="roleName">登录时的角色</param>
/// <returns></returns>
public Claim[] BuildClaims(UserData userData)
{
// 配置用户标识
var userClaims = new Claim[]
{
new Claim(UserClaimType.Id,userData.Id.ToString()),//id
new Claim(UserClaimType.Account,userData.Account),//account
new Claim(UserClaimType.Name,userData.Name),//name
new Claim(UserClaimType.RoleName,userData.RoleName),//rolename
new Claim(UserClaimType.RoleDisplayName,userData.RoleDisplayName),//roledisplayname
new Claim(JwtRegisteredClaimNames.Jti,userData.Id.ToString()),
new Claim(JwtRegisteredClaimNames.Iat, DateTime.Now.ToString()),
//new Claim(JwtRegisteredClaimNames.Iss,_jwtSetting.Issuer),
//new Claim(JwtRegisteredClaimNames.Aud,_jwtSetting.Audience),
//new Claim(JwtRegisteredClaimNames.Nbf,$"{new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds()}") ,
//这个就是过期时间,可自定义,注意JWT有自己的缓冲过期时间
//new Claim (JwtRegisteredClaimNames.Exp,$"{new DateTimeOffset(DateTime.Now.Add(_tokenLifeTime)).ToUnixTimeSeconds()}"),
};
return userClaims;
} /// <summary>
/// 生成jwt令牌
/// </summary>
/// <param name="claims">自定义的claim</param>
/// <returns></returns>
public string BuildToken(Claim[] claims)
{
var nowTime = DateTime.Now;
var creds = new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtSetting.SecurityKey)), SecurityAlgorithms.HmacSha256);
JwtSecurityToken tokenkey = new JwtSecurityToken(
issuer: _jwtSetting.Issuer,
audience: _jwtSetting.Audience,
claims: claims,
notBefore: nowTime,
expires: nowTime.Add(_tokenLifeTime),
signingCredentials: creds); return new JwtSecurityTokenHandler().WriteToken(tokenkey);
}
}
}
  • 这个是jwt核心的生成token服务类,可以把它以单例的形式注册在ioc容器中
  • 调用的时候,先生成用户身份信息
  • 再将用户身份信息生成token,此时在JwtSecurityToken中定义了token的过期时间、颁发时间、加密方式等

封装Ioc注册

MS.Component.Jwt类库中新建JwtServiceExtensions.cs类:

using MS.Component.Jwt.UserClaim;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Text; namespace MS.Component.Jwt
{
public static class JwtServiceExtensions
{
public static IServiceCollection AddJwtService(this IServiceCollection services, IConfiguration configuration)
{
//绑定appsetting中的jwtsetting
services.Configure<JwtSetting>(configuration.GetSection(nameof(JwtSetting))); //注册jwtservice
services.AddSingleton<JwtService>();
//注册IHttpContextAccessor
services.AddScoped<IHttpContextAccessor, HttpContextAccessor>();
services.AddScoped<IClaimsAccessor, ClaimsAccessor>(); var jwtConfig = configuration.GetSection("JwtSetting"); services
.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(o =>
{
o.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtConfig["SecurityKey"])), ValidateIssuer = true,
ValidIssuer = jwtConfig["Issuer"], ValidateAudience = true,
ValidAudience = jwtConfig["Audience"], //总的Token有效时间 = JwtRegisteredClaimNames.Exp + ClockSkew ;
RequireExpirationTime = true,
ValidateLifetime = true,// 是否验证Token有效期,使用当前时间与Token的Claims中的NotBefore和Expires对比.同时启用ClockSkew
ClockSkew = TimeSpan.Zero //注意这是缓冲过期时间,总的有效时间等于这个时间加上jwt的过期时间,如果不配置,默认是5分钟 };
});
return services;
}
}
}
  • 绑定appsetting中的jwtsetting
  • 以单例形式注册jwtservice
  • 注册IHttpContextAccessor和IClaimsAccessor为Scoped生命周期(网上很多文章都把IHttpContextAccessor的生命周期定义为单例,我不是很理解,我认为Scoped更好,如果有明白的小伙伴可以给我指点下)
  • IHttpContextAccessor是ASP.NET Core自带的接口,而IClaimsAccessor是我自己对IHttpContextAccessor的一个封装,所以这两个接口的注册生命周期保持了一致
  • 根据appsettings.json中的配置,启用jwt验证服务AddJwtBearer:
    • IssuerSigningKey定义了加密密钥,而ValidateIssuerSigningKey = true启用了密钥验证
    • ValidateIssuer、ValidIssuer和ValidateAudience、ValidAudience这两对同上
    • 注意token有效时间的计算方法,总的Token有效时间 = JwtRegisteredClaimNames.Exp + ClockSkew
    • 这里把ClockSkew缓冲时间改成了0,默认是5分钟(也就是去掉了缓冲时间)

注册Jwt服务

MS.WebApi应用程序的Startup.cs类中,ConfigureServices加上services.AddJwtService(Configuration);

开启认证中间件

MS.WebApi应用程序的Startup.cs类中,中间件配置加上app.UseAuthentication();以开启认证中间件:

  • 注意app.UseAuthentication()是认证中间件,而app.UseAuthorization()是授权中间件
  • 中间件的顺序不能随意调整!

至此关于开启jwt授权验证、开启认证中间件、jwt服务注册都已完成

  1. 网站设定好JWT配置,例如颁发者、密钥、token的过期时间
  2. 用户输入账号密码进行登录,网站验证成功后调用JwtService生成并返回一个token给前端
  3. 用户在之后的请求中都会携带好这个token,而用户的信息就存在token中
  4. ASP.NET Core中有个IHttpContextAccessor接口,可以访问每次请求的上下文,从而可以让后端获取到当前请求的token中的用户信息
  5. 我这里对IHttpContextAccessor接口做了一个封装,叫IClaimsAccessor,所以可以直接通过IClaimsAccessor获取到用户信息
  6. 如果token过期、用户未登录,api接口调用会返回错误代码401未认证

用户登录

LoginViewModel.cs

MS.Models类库中,在ViewModel文件夹下新建LoginViewModel.cs类:

using AutoMapper;
using Microsoft.EntityFrameworkCore;
using MS.Common.Security;
using MS.Component.Jwt.UserClaim;
using MS.DbContexts;
using MS.Entities;
using MS.Entities.Core;
using MS.UnitOfWork;
using MS.WebCore;
using MS.WebCore.Core;
using System;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks; namespace MS.Models.ViewModel
{
public class LoginViewModel
{
[Display(Name = "用户名")]
[Required(ErrorMessage = "{0}必填")]
[StringLength(16, ErrorMessage = "不能超过{0}个字符")]
[RegularExpression(@"^[a-zA-Z0-9_]{4,16}$", ErrorMessage = "只能包含字符、数字和下划线")]
public string Account { get; set; }
[Display(Name = "密码")]
[Required(ErrorMessage = "{0}必填")]
public string Password { get; set; } public async Task<ExecuteResult<UserData>> LoginValidate(IUnitOfWork<MSDbContext> unitOfWork, IMapper mapper, SiteSetting siteSetting)
{
ExecuteResult<UserData> result = new ExecuteResult<UserData>();
//将登录用户查出来
var loginUserInDB = await unitOfWork.GetRepository<UserLogin>().FindAsync(Account); //用户不存在
if (loginUserInDB is null)
{
return result.SetFailMessage("用户不存在");
} //用户被锁定
if (loginUserInDB.IsLocked &&
loginUserInDB.LockedTime.HasValue &&
(DateTime.Now - loginUserInDB.LockedTime.Value).Minutes < siteSetting.LoginLockedTimeout)
{
return result.SetFailMessage(string.Format("用户已被锁定,请{0}分钟后再试!", siteSetting.LoginLockedTimeout.ToString()));
} //密码正确
if (Crypto.VerifyHashedPassword(loginUserInDB.HashedPassword, Password))
{
//密码正确后才加载用户信息、角色信息
var userInDB = await unitOfWork.GetRepository<User>().GetFirstOrDefaultAsync(
predicate: a => a.Id == loginUserInDB.UserId,
include: source => source
.Include(u => u.Role)); //如果用户已失效
if (userInDB.StatusCode != StatusCode.Enable)
{
return result.SetFailMessage("用户已失效,请联系管理员!");
} //用户正常、密码正确,更新相应字段
loginUserInDB.IsLocked = false;
loginUserInDB.AccessFailedCount = 0;
loginUserInDB.LastLoginTime = DateTime.Now;
//提交到数据库
await unitOfWork.SaveChangesAsync(); //得到userdata
UserData userData = mapper.Map<UserData>(userInDB);
return result.SetData(userData);
}
//密码错误
else
{
loginUserInDB.AccessFailedCount++;//失败次数累加
result.SetFailMessage("用户名或密码错误!");
//超出失败次数限制
if (loginUserInDB.AccessFailedCount >= siteSetting.LoginFailedCountLimits)
{
loginUserInDB.IsLocked = true;
loginUserInDB.LockedTime = DateTime.Now;
result.SetFailMessage(string.Format("用户已被锁定,请{0}分钟后再试!", siteSetting.LoginLockedTimeout.ToString()));
}
//提交到数据库
await unitOfWork.SaveChangesAsync();
return result;
}
}
}
}

在LoginViewModel中做了核心的登录验证,除了验证密码,还会校验用户密码错误次数,失败次数(LoginFailedCountLimits)过多会锁定账号,在指定时间(LoginLockedTimeout)后才能继续登录,这两个配置在SiteSetting中

UserProfile.cs映射配置

MS.Models类库中,在Automapper文件夹下新建UserProfile.cs类:

using AutoMapper;
using MS.Component.Jwt.UserClaim;
using MS.Entities; namespace MS.Models.Automapper
{
public class UserProfile : Profile
{
public UserProfile()
{
CreateMap<User, UserData>()
.ForMember(a => a.Id, t => t.MapFrom(b => b.Id))
.ForMember(a => a.RoleName, t => t.MapFrom(b => b.Role.Name))
.ForMember(a => a.RoleDisplayName, t => t.MapFrom(b => b.Role.DisplayName))
;
}
}
}

建立了User到UserData的映射配置

账号服务

MS.Services类库下新建Account文件夹,在该文件夹下新建IAccountService.csAccountService.cs类:

IAccountService.cs:

using MS.Component.Jwt.UserClaim;
using MS.Models.ViewModel;
using MS.WebCore.Core;
using System.Threading.Tasks; namespace MS.Services
{
public interface IAccountService : IBaseService
{
Task<ExecuteResult<UserData>> Login(LoginViewModel viewModel);
}
}

AccountService.cs:

using AutoMapper;
using Microsoft.Extensions.Options;
using MS.Common.IDCode;
using MS.Component.Jwt;
using MS.Component.Jwt.UserClaim;
using MS.DbContexts;
using MS.Models.ViewModel;
using MS.UnitOfWork;
using MS.WebCore;
using MS.WebCore.Core;
using System.Threading.Tasks; namespace MS.Services
{
public class AccountService : BaseService, IAccountService
{
private readonly JwtService _jwtService;
private readonly SiteSetting _siteSetting; public AccountService(JwtService jwtService, IOptions<SiteSetting> options, IUnitOfWork<MSDbContext> unitOfWork, IMapper mapper, IdWorker idWorker) : base(unitOfWork, mapper, idWorker)
{
_jwtService = jwtService;
_siteSetting = options.Value;
} public async Task<ExecuteResult<UserData>> Login(LoginViewModel viewModel)
{
var result = await viewModel.LoginValidate(_unitOfWork, _mapper, _siteSetting);
if (result.IsSucceed)
{
result.Result.Token = _jwtService.BuildToken(_jwtService.BuildClaims(result.Result));
return new ExecuteResult<UserData>(result.Result);
}
else
{
return new ExecuteResult<UserData>(result.Message);
}
}
}
}
  • 目前就实现了Login逻辑,密码验证成功后,将用户信息交给JwtService生成token
  • 之后还有修改密码等行为,也都写在这个接口里

登录接口

MS.WebApi应用程序的Controllers文件夹下新建Base文件夹,在该文件夹下新建AuthorizeController.cs类:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; namespace MS.WebApi.Controllers
{
[Route("[controller]")]
[Authorize]
public class AuthorizeController : ControllerBase
{
}
}
  • 注意命名空间依然是MS.WebApi.Controllers
  • AuthorizeController类上打上了[Authorize]特性,表示需要认证授权后才能访问

Controllers文件夹下新建AccountController.cs类:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using MS.Component.Jwt.UserClaim;
using MS.Models.ViewModel;
using MS.Services;
using MS.WebCore.Core;
using System.Threading.Tasks; namespace MS.WebApi.Controllers
{
[Route("[controller]")]
[ApiController]
public class AccountController : AuthorizeController
{
private readonly IAccountService _accountService; public AccountController(IAccountService accountService)
{
_accountService = accountService;
} [HttpPost]
[AllowAnonymous]
public async Task<ExecuteResult<UserData>> Login(LoginViewModel viewModel)
{
return await _accountService.Login(viewModel);
}
}
}
  • 可以看到,AccountController已经继承了刚刚的AuthorizeController,所以AccountController内的资源也都要授权后才能访问
  • Login方法上打了[AllowAnonymous]特性,所以Login未授权也可以访问(用户登录的接口肯定不能有认证限制)

将RoleController.cs的基类也修改为AuthorizeController:

访问授权接口

至此所有的授权验证已经完成了,启动项目,打开Postman,依旧是访问role接口,会提示401:

在Postman的MSDemo中,新建一个Login请求localhost:5000/account,json参数为(这是种子数据中的默认超级管理员账号):

{
"Account":"admin",
"Password":"admin"
}

点击发送,可以看到登录成功,返回了用户信息及token:

我们复制这段token,右击MSDemo-Edit-Authorization-TYPE(Bearer Token)-把复制的token粘贴进去:

此时,MSDemo里所有的接口请求时,都会带上这段token,就不需要每个请求单独添加一次token了

也可以看到添加上token后,接口访问又请求成功了

补全RoleService

之前做角色增删改的时候,创建者和修改者都是临时代码,不是当前用户真实Id,这会儿登录做好了可以补全了:

BaseService中添加公开类型的IClaimsAccessor成员,AccountService和RoleService的构造函数都要重构一下

在RoleService中如下图获取和使用用户信息:

项目完成后,如下图:

ASP.NET Core搭建多层网站架构【10-使用JWT进行授权验证】的更多相关文章

  1. ASP.NET Core搭建多层网站架构【2-公共基础库】

    2020/01/28, ASP.NET Core 3.1, VS2019,Newtonsoft.Json 12.0.3, Microsoft.AspNetCore.Cryptography.KeyDe ...

  2. ASP.NET Core搭建多层网站架构【5-网站数据库实体设计及映射配置】

    2020/01/29, ASP.NET Core 3.1, VS2019, EntityFrameworkCore 3.1.1, Microsoft.Extensions.Logging.Consol ...

  3. ASP.NET Core搭建多层网站架构【7-使用NLog日志记录器】

    2020/01/29, ASP.NET Core 3.1, VS2019, NLog.Web.AspNetCore 4.9.0 摘要:基于ASP.NET Core 3.1 WebApi搭建后端多层网站 ...

  4. ASP.NET Core搭建多层网站架构【14-扩展之部署到IIS】

    2020/02/03, ASP.NET Core 3.1, VS2019, IIS 10, dotnet-hosting-3.1.1-win.exe 摘要:基于ASP.NET Core 3.1 Web ...

  5. ASP.NET Core搭建多层网站架构【0-前言】

    2020/01/26, ASP.NET Core 3.1, VS2019 摘要:基于ASP.NET Core 3.1 WebApi搭建后端多层网站架构 目录 0-前言 1-项目结构分层建立 2-公共基 ...

  6. ASP.NET Core搭建多层网站架构【1-项目结构分层建立】

    2020/01/26, ASP.NET Core 3.1, VS2019 摘要:基于ASP.NET Core 3.1 WebApi搭建后端多层网站架构[1-项目结构分层建立] 文章目录 此分支项目代码 ...

  7. ASP.NET Core搭建多层网站架构【3-xUnit单元测试之简单方法测试】

    2020/01/28, ASP.NET Core 3.1, VS2019, xUnit 2.4.0 摘要:基于ASP.NET Core 3.1 WebApi搭建后端多层网站架构[3-xUnit单元测试 ...

  8. ASP.NET Core搭建多层网站架构【4-工作单元和仓储设计】

    2020/01/28, ASP.NET Core 3.1, VS2019, Microsoft.EntityFrameworkCore.Relational 3.1.1 摘要:基于ASP.NET Co ...

  9. ASP.NET Core搭建多层网站架构【6-注册跨域、网站核心配置】

    2020/01/29, ASP.NET Core 3.1, VS2019, NLog.Web.AspNetCore 4.9.0 摘要:基于ASP.NET Core 3.1 WebApi搭建后端多层网站 ...

  10. ASP.NET Core搭建多层网站架构【8.1-使用ViewModel注解验证】

    2020/01/29, ASP.NET Core 3.1, VS2019 摘要:基于ASP.NET Core 3.1 WebApi搭建后端多层网站架构[8.1-使用ViewModel注解验证] 使用V ...

随机推荐

  1. Idea全部快捷键+自行修改快捷键

    Idea常用快捷键 Tab,代码标签输入完成后,按 Tab,生成代码 Ctrl+E,最近的文件 Ctrl+X,删除行 Ctrl+D,复制行 Alt+1,快速打开或隐藏工程面板 ctrl+alt+t 快 ...

  2. javascript当中Function用法

    4)Function用法 例 3.4.1 <head>    <meta http-equiv="content-type" content="text ...

  3. mini-batch是什么 以及dataloader的作用

    mini-batch是什么 以及dataloader的作用 待办 我们在训练神经网络时,使用的是mini-batch(一次输入多张图片),所以我们在使用一个叫DataLoader的工具为我们将5000 ...

  4. pytorch怎么入门学习

    pytorch怎么入门学习 https://www.zhihu.com/question/55720139

  5. group by分组后对组内数据进行排序

    查询 每个班级英语成绩最高的前两名的记录 原文:https://www.cnblogs.com/hxfcodelife/p/10226934.html select a.Classid,a.Engli ...

  6. testng的注解

    今天又学了点testng的新知识.原来在testng执行用例时,同一个class中的各个method按照字母顺序执行.为了实现自定义顺序执行,怎么办呢? 加入注解priority,举例如下: http ...

  7. Customized Mini LED Keychain For Better Brand Identity

    Looking for products that tell people the brand name? Then you'll find an affordable product that wi ...

  8. 给大家推荐一些Java初学者所看的书籍

    一.适合初学者的经典Java书籍; 比方说<Java核心技术卷>,<Effective Java中文版(第2版)> 二.Java开发者必读: <clean code> ...

  9. openshift3.10集群部署

    简介 openshift是基于k8s的开源容器云. 要求 系统环境:CentOS 7.5 搭建一个master节点,两个node节点 注意: openshift3 依赖docker的版本为1.13.1 ...

  10. 工具 - deepin vscode中的oh-my-zsh乱码

    解决办法 https://blog.zhaytam.com/2019/04/19/powerline-and-zshs-agnoster-theme-in-vs-code/ git clone htt ...