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. centos7 walle2瓦力部署教程

    项目部署上线,如果是单服务器,那么有多种方式可以部署,比如直接ftp上传,或者直接git去拉取,人工操作也不会花费精力和时间,但是如果采用了集群模式,有多台服务器,那么依靠一台一台的去上传代码,就显得 ...

  2. C++-1019-Number Sequence

    题意: 求数字112123123412345123456123456712345678123456789123456789101234567891011123456789101112123456789 ...

  3. AcWing 3. 完全背包问题

    朴素 #include<iostream> #include<algorithm> using namespace std ; ; int n,m; int v[N],w[N] ...

  4. Virtual Judge POJ 1002 487-3279

    模拟 #include<iostream> #include<algorithm> #include<string.h> #include<stdio.h&g ...

  5. koa2第一天 安装koa2

    安装全局koa2:npm install -g koa2 -generator 创建一个koa2文件夹:koa2 -e koa2 进入koa2文件夹:cd koa2 安装npm模块:npm insta ...

  6. 题解【SP8002】HORRIBLE - Horrible Queries

    题面 题解 这是一道线段树的模板题. 题目需要我们维护一个支持区间修改.区间查询的一个数据结构,很容易想到线段树. 然后发现和洛谷上线段树的模板1是同一道题. 由于本题中每个数的初始值都为\(0\), ...

  7. linux 系统如何复制文件到指定目录

    首先使用命令函数“cp”  即copy的缩写,    一般模式: cp filename 路径. 如file1在A目录下,我们想把file1复制到B目录下 命令为  cp file1 /home/B ...

  8. DSP---TI CCSv5.5.x-Windows安装

    TI CCSv5.5.x(正式版)-Windows版本 国内2013年9月13日首发安装CCSv5.5图示 *请关掉防火墙及杀毒软件进行安装 第一步 第二步 安装程序检测到挂起的重新启动,这可能在安装 ...

  9. Javaweb项目的命名规范

    项目名称:一般是英文 包名:公司域名的倒写,例如com.baidu 数据访问层:dao,persist,mapper 实体:entity,model,bean,javabean,pojo 业务逻辑:s ...

  10. 同步块:synchronized(同步监视器对象){同步运行代码片段}

    package seday10; import seday03.Test2; /** * @author xingsir * 同步块:synchronized(同步监视器对象){需要同步运行的代码片段 ...