近日,应一位朋友的邀请写了个Asp.Net Core基于JWT认证的数据接口网关Demo。朋友自己开了个公司,接到的一个升级项目,客户要求用Aps.Net Core做数据网关服务且基于JWT认证实现对前后端分离的数据服务支持,于是想到我一直做.Net开发,问我是否对.Net Core有所了解?能不能做个简单Demo出来看看?我说,分道扬镳之后我不是调用别人的接口就是提供接口给别人调用,于是便有了以下示例代码。

示例要求能演示获取Token及如何使用该Token访问数据资源,在Demo中实现了JWT的颁发及验证以及重写一个ActionAuthorizeAttribute实现对具体数据接口的调用权限控制,先看一下项目截图:

[项目截图]

项目文件介绍

解决方案下只有一个项目,项目名称就叫Jwt.Gateway,包含主要文件有:

  1. Controllers目录下的ApiActionFilterAttribute.cs文件,继承Microsoft.AspNetCore.Mvc.Filters.ActionFilterAttribute,用于校验接口调用者对具体接口的访问权限。
  2. Controllers目录下的ApiBase.cs文件,继承Microsoft.AspNetCore.Mvc.Controller,具有Microsoft.AspNetCore.Authorization.Authorize特性引用,用于让所有数据接口用途的控制器继承,定义有CurrentAppKey属性(来访应用程序的身份标识)并在OnActionExecuting事件中统一分析Claims并赋值。
  3. Controllers目录下的TokenController.cs控制器文件,用于对调用方应用程序获取及注销Token。
  4. Controllers目录下的UsersController.cs控制器文件,继承ApiBase.cs,作为数据调用示例。
  5. MiddleWares目录下的ApiCustomException.cs文件,是一个数据接口的统一异常处理中间件。
  6. Models目录下的ApiResponse.cs文件,用于做数据接口的统一数据及错误信息输出实体模型。
  7. Models目录下的User.cs文件,示例数据实体模型。
  8. Program.csStartup.cs文件就不介绍了,随便建个空项目都有。

项目文件代码

ApiActionFilterAttribute.cs

Controllers目录下的ApiActionFilterAttribute.cs文件,继承Microsoft.AspNetCore.Mvc.Filters.ActionFilterAttribute,用于校验接口调用者对具体接口的访问权限。

设想每一个到访的请求都是一个应用程序,每一个应用程序都分配有基本的Key和Password,每一个应用程序具有不同的接口访问权限,所以在具体的数据接口上应该声明该接口所要求的权限值,比如修改用户信息的接口应该在接口方法上声明需要具有“修改用户”的权限,用例:[ApiActionFilter("用户修改")]。

大部分情况下一个接口(方法)对应一个操作,这样基本上就能应付了,但是不排除有时候可能需要多个权限组合进行验证,所以该文件中有一个对多个权限值进行校验的“”和“”枚举,用例:[ApiActionFilter(new string[] { "用户修改", "用户录入", "用户删除" },ApiActionFilterAttributeOption.AND)],这样好像就差不多了。

由于在一个接口调用之后可能需要将该接口所声明需要的权限值记入日志等需求,因此权限值集合将被写入到HttpContext.Items["Permissions"]中以方便可能的后续操作访问,看代码:

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Filters; namespace Jwt.Gateway.Controllers
{
public enum ApiActionFilterAttributeOption
{
OR,AND
}
public class ApiActionFilterAttribute : Microsoft.AspNetCore.Mvc.Filters.ActionFilterAttribute
{
List<string> Permissions = new List<string>();
ApiActionFilterAttributeOption Option = ApiActionFilterAttributeOption.AND;
public ApiActionFilterAttribute(string permission)
{
Permissions.Add(permission);
}
public ApiActionFilterAttribute(string[] permissions, ApiActionFilterAttributeOption option)
{
foreach(var permission in permissions) {
if (Permissions.Contains(permission))
{
continue;
}
Permissions.Add(permission);
}
Option = option;
} public override void OnActionExecuting(ActionExecutingContext context)
{
var key = GetAppKey(context);
List<string> keyPermissions = GetAppKeyPermissions(key);
var isAnd = Option == ApiActionFilterAttributeOption.AND;
var permissionsCount = Permissions.Count;
var keyPermissionsCount = keyPermissions.Count;
for (var i = ; i < permissionsCount; i++)
{
bool flag = false;
for (var j = ; j < keyPermissions.Count; j++)
{
if (flag = string.Equals(Permissions[i], keyPermissions[j], StringComparison.OrdinalIgnoreCase))
{
break;
}
}
if (flag)
{
continue;
}
if (isAnd)
{
throw new Exception("应用“" + key + "”缺少“" + Permissions[i] + "”的权限");
}
} context.HttpContext.Items.Add("Permissions", Permissions); base.OnActionExecuting(context);
} private string GetAppKey(ActionExecutingContext context)
{
var claims = context.HttpContext.User.Claims;
if (claims == null)
{
throw new Exception("未能获取到应用标识");
}
var claimKey = claims.ToList().Find(o => string.Equals(o.Type, "AppKey", StringComparison.OrdinalIgnoreCase));
if (claimKey == null)
{
throw new Exception("未能获取到应用标识");
} return claimKey.Value;
}
private List<string> GetAppKeyPermissions(string appKey)
{
List<string> li = new List<string>
{
"用户明细","用户列表","用户录入","用户修改","用户删除"
};
return li;
} }
}

ApiActionAuthorizeAttribute.cs

ApiBase.cs

Controllers目录下的ApiBase.cs文件,继承Microsoft.AspNetCore.Mvc.Controller,具有Microsoft.AspNetCore.Authorization.Authorize特性引用,用于让所有数据接口用途的控制器继承,定义有CurrentAppKey属性(来访应用程序的身份标识)并在OnActionExecuting事件中统一分析Claims并赋值。

通过验证之后,Aps.Net Core会在HttpContext.User.Claims中将将来访者的身份信息记录下来,我们可以通过该集合得到来访者的身份信息。

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters; namespace Jwt.Gateway.Controllers
{
[Microsoft.AspNetCore.Authorization.Authorize]
public class ApiBase : Microsoft.AspNetCore.Mvc.Controller
{
private string _CurrentAppKey = "";
public string CurrentAppKey { get { return _CurrentAppKey; } }
public override void OnActionExecuting(ActionExecutingContext context)
{
var claims = context.HttpContext.User.Claims.ToList();
var claim = claims.Find(o => o.Type == "appKey");
if (claim == null)
{
throw new Exception("未通过认证");
}
var appKey = claim.Value;
if (string.IsNullOrEmpty(appKey))
{
throw new Exception("appKey不合法");
} _CurrentAppKey = appKey; base.OnActionExecuting(context);
}
}
}

ApiBase.cs

TokenController.cs

Controllers目录下的TokenController.cs控制器文件,用于对调用方应用程序获取及注销Token。

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; namespace Jwt.Gateway.Controllers
{
[Route("api/[controller]/[action]")]
public class TokenController : Controller
{
private readonly Microsoft.Extensions.Configuration.IConfiguration _configuration; public TokenController(Microsoft.Extensions.Configuration.IConfiguration configuration)
{
_configuration = configuration;
} // /api/token/get
public IActionResult Get(string appKey, string appPassword)
{
try
{
if (string.IsNullOrEmpty(appKey))
{
throw new Exception("缺少appKey");
}
if (string.IsNullOrEmpty(appKey))
{
throw new Exception("缺少appPassword");
}
if (appKey != "myKey" && appPassword != "myPassword")//固定的appKey及appPassword,实际项目中应该来自数据库或配置文件
{
throw new Exception("配置不存在");
} var key = new Microsoft.IdentityModel.Tokens.SymmetricSecurityKey(System.Text.Encoding.UTF8.GetBytes(_configuration["JwtSecurityKey"]));
var creds = new Microsoft.IdentityModel.Tokens.SigningCredentials(key, Microsoft.IdentityModel.Tokens.SecurityAlgorithms.HmacSha256);
var claims = new List<System.Security.Claims.Claim>();
claims.Add(new System.Security.Claims.Claim("appKey", appKey));//仅在Token中记录appKey
var token = new System.IdentityModel.Tokens.Jwt.JwtSecurityToken(
issuer: _configuration["JwtTokenIssuer"],
audience: _configuration["JwtTokenAudience"],
claims: claims,
expires: DateTime.Now.AddMinutes(),
signingCredentials: creds); return Ok(new Models.ApiResponse { status = , message = "OK", data = new System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler().WriteToken(token) }); }
catch(Exception ex)
{
return Ok(new Models.ApiResponse { status = , message = ex.Message, data = "" });
}
} // /api/token/delete
public IActionResult Delete(string token)
{
//code: 加入黑名单,使其无效 return Ok(new Models.ApiResponse { status = , message = "OK", data = "" });
} }
}

TokenController.cs

UsersController.cs

Controllers目录下的UsersController.cs控制器文件,继承ApiBase.cs,作为数据调用示例。

该控制器定义了对User对象常规的 明细、列表、录入、修改、删除 等操作。

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc; namespace Jwt.Gateway.Controllers
{
[Produces("application/json")]
[Route("api/[controller]/[action]")]
public class UsersController : ApiBase
{
/*
* 1.要访问访问该控制器提供的接口请先通过"/api/token/get"获取token
* 2.访问该控制器提供的接口http请求头必须具有值为"Bearer+空格+token"的Authorization键,格式参考:
* "Authorization"="Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiQXBwIiwiYXBwS2V5IjoibXlLZXkiLCJleHAiOjE1NTE3ODc2MDMsImlzcyI6IkdhdGV3YXkiLCJhdWQiOiJhdWRpZW5jZSJ9.gQ9_Q7HUT31oFyfl533T-bNO5IWD2drl0NmD1JwQkMI"
*/ /// <summary>
/// 临时用户测试数据,实际项目中应该来自数据库等媒介
/// </summary>
static List<Models.User> _Users = null;
static object _Lock = new object();
public UsersController()
{
if (_Users == null)
{
lock (_Lock)
{
if (_Users == null)
{
_Users = new List<Models.User>();
var now = DateTime.Now;
for(var i = ; i < ; i++)
{
var num = i + ;
_Users.Add(new Models.User { UserId = num, UserName = "name"+num, UserPassword = "pwd"+num, UserJoinTime = now });
}
}
}
}
} // /api/users/detail
[ApiActionFilter("用户明细")]
public IActionResult Detail(long userId)
{
/*
//获取appKey(在ApiBase中写入)
var appKey = CurrentAppKey;
//获取使用的权限(在ApiActionAuthorizeAttribute中写入)
var permissions = HttpContext.Items["Permissions"];
*/ var user = _Users.Find(o => o.UserId == userId);
if (user == null)
{
throw new Exception("用户不存在");
} return Ok(new Models.ApiResponse { data = user, status = , message = "OK" });
} // /api/users/list
[ApiActionFilter("用户列表")]
public IActionResult List(int page, int size)
{
page = page < ? : page;
size = size < ? : size;
var total = _Users.Count();
var pages = total % size == ? total / size : ((long)Math.Floor((double)total / size + ));
if (page > pages)
{
return Ok(new Models.ApiResponse { data = new List<Models.User>(), status = , message = "OK", total = total });
}
var li = new List<Models.User>();
var startIndex = page * size - size;
var endIndex = startIndex + size - ;
if (endIndex > total - )
{
endIndex = total - ;
}
for(; startIndex <= endIndex; startIndex++)
{
li.Add(_Users[startIndex]);
}
return Ok(new Models.ApiResponse { data = li, status = , message = "OK", total = total });
} // /api/users/add
[ApiActionFilter("用户录入")]
public IActionResult Add()
{
return Ok(new Models.ApiResponse { status = , message = "OK" });
} // /api/users/update
[ApiActionFilter(new string[] { "用户修改", "用户录入", "用户删除" },ApiActionFilterAttributeOption.AND)]
public IActionResult Update()
{
return Ok(new Models.ApiResponse { status = , message = "OK" });
} // /api/users/delete
[ApiActionFilter("用户删除")]
public IActionResult Delete()
{
return Ok(new Models.ApiResponse { status = , message = "OK" });
}
}
}

UsersController.cs

ApiCustomException.cs

MiddleWares目录下的ApiCustomException.cs文件,是一个数据接口的统一异常处理中间件。

该文件整理并抄袭自:https://www.cnblogs.com/ShenNan/p/10197231.html

在此特别感谢一下作者的先行贡献,并请原谅我无耻的抄袭。

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection; namespace Jwt.Gateway.MiddleWares
{
//参考: https://www.cnblogs.com/ShenNan/p/10197231.html public enum ApiCustomExceptionHandleType
{
JsonHandle = ,
PageHandle = ,
Both =
}
public class ApiCustomExceptionMiddleWareOption
{
public ApiCustomExceptionMiddleWareOption(
ApiCustomExceptionHandleType handleType = ApiCustomExceptionHandleType.JsonHandle,
IList<PathString> jsonHandleUrlKeys = null,
string errorHandingPath = "")
{
HandleType = handleType;
JsonHandleUrlKeys = jsonHandleUrlKeys;
ErrorHandingPath = errorHandingPath;
}
public ApiCustomExceptionHandleType HandleType { get; set; }
public IList<PathString> JsonHandleUrlKeys { get; set; }
public PathString ErrorHandingPath { get; set; }
}
public class ApiCustomExceptionMiddleWare
{
private RequestDelegate _next;
private ApiCustomExceptionMiddleWareOption _option;
private IDictionary<int, string> _exceptionStatusCodeDic; public ApiCustomExceptionMiddleWare(RequestDelegate next, ApiCustomExceptionMiddleWareOption option)
{
_next = next;
_option = option;
_exceptionStatusCodeDic = new Dictionary<int, string>
{
{ , "未授权的请求" },
{ , "找不到该页面" },
{ , "访问被拒绝" },
{ , "服务器发生意外的错误" }
//其余状态自行扩展
};
} public async Task Invoke(HttpContext context)
{
Exception exception = null;
try
{
await _next(context);
}
catch (Exception ex)
{
context.Response.Clear();
context.Response.StatusCode = ;//手动设置状态码(总是成功)
exception = ex;
}
finally
{
if (_exceptionStatusCodeDic.ContainsKey(context.Response.StatusCode) &&
!context.Items.ContainsKey("ExceptionHandled"))
{
var errorMsg = string.Empty;
if (context.Response.StatusCode == && exception != null)
{
errorMsg = $"{_exceptionStatusCodeDic[context.Response.StatusCode]}\r\n{(exception.InnerException != null ? exception.InnerException.Message : exception.Message)}";
}
else
{
errorMsg = _exceptionStatusCodeDic[context.Response.StatusCode];
}
exception = new Exception(errorMsg);
}
if (exception != null)
{
var handleType = _option.HandleType;
if (handleType == ApiCustomExceptionHandleType.Both)
{
var requestPath = context.Request.Path;
handleType = _option.JsonHandleUrlKeys != null && _option.JsonHandleUrlKeys.Count(
k => requestPath.StartsWithSegments(k, StringComparison.CurrentCultureIgnoreCase)) > ?
ApiCustomExceptionHandleType.JsonHandle :
ApiCustomExceptionHandleType.PageHandle;
} if (handleType == ApiCustomExceptionHandleType.JsonHandle)
await JsonHandle(context, exception);
else
await PageHandle(context, exception, _option.ErrorHandingPath);
}
}
}
private Jwt.Gateway.Models.ApiResponse GetApiResponse(Exception ex)
{
return new Jwt.Gateway.Models.ApiResponse() { status = , message = ex.Message };
}
private async Task JsonHandle(HttpContext context, Exception ex)
{
var apiResponse = GetApiResponse(ex);
var serialzeStr = Newtonsoft.Json.JsonConvert.SerializeObject(apiResponse);
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(serialzeStr, System.Text.Encoding.UTF8);
}
private async Task PageHandle(HttpContext context, Exception ex, PathString path)
{
context.Items.Add("Exception", ex);
var originPath = context.Request.Path;
context.Request.Path = path;
try
{
await _next(context);
}
catch { }
finally
{
context.Request.Path = originPath;
}
}
}
public static class ApiCustomExceptionMiddleWareExtensions
{
public static IApplicationBuilder UseApiCustomException(this IApplicationBuilder app, ApiCustomExceptionMiddleWareOption option)
{
return app.UseMiddleware<ApiCustomExceptionMiddleWare>(option);
}
}
}

ApiCustomException.cs

配置相关

appsettings.json

算法'HS256'要求SecurityKey.KeySize大于'128'位,所以JwtSecurityKey可不要太短了哦。

 {
"Urls": "http://localhost:60000",
"AllowedHosts": "*",
"JwtSecurityKey": "areyouokhhhhhhhhhhhhhhhhhhhhhhhhhhh",
"JwtTokenIssuer": "Jwt.Gateway",
"JwtTokenAudience": "App"
}

appsettings.json

Startup.cs

关于JWT的配置可以在通过JwtBearerOptions加入一些自己的事件处理逻辑,共有4个事件可供调用:OnAuthenticationFailed,OnMessageReceived,OnTokenValidated,OnChallenge, 本示例中是在OnTokenValidated中插入Token黑名单的校验逻辑。黑名单应该是Jwt应用场景中主动使Token过期的主流做法了。

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Jwt.Gateway.MiddleWares;
using Microsoft.Extensions.DependencyInjection; namespace Jwt.Gateway
{
public class Startup
{
private readonly Microsoft.Extensions.Configuration.IConfiguration _configuration; public Startup(Microsoft.Extensions.Configuration.IConfiguration configuration)
{
_configuration = configuration;
} public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options => {
options.Events = new Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerEvents
{
/*OnMessageReceived = context =>
{
context.Token = context.Request.Query["access_token"];
return Task.CompletedTask;
},*/
OnTokenValidated = context =>
{
var token = ((System.IdentityModel.Tokens.Jwt.JwtSecurityToken)context.SecurityToken).RawData;
if (InBlacklist(token))
{
context.Fail("token in blacklist");
}
return Task.CompletedTask;
}
};
options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidAudience = _configuration["JwtTokenAudience"],
ValidIssuer = _configuration["JwtTokenIssuer"],
IssuerSigningKey = new Microsoft.IdentityModel.Tokens.SymmetricSecurityKey(System.Text.Encoding.UTF8.GetBytes(_configuration["JwtSecurityKey"]))
};
});
services.AddMvc().AddJsonOptions(option=> {
option.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:mm:ss.fff";
});
} public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
} app.UseApiCustomException(new ApiCustomExceptionMiddleWareOption(
handleType: ApiCustomExceptionHandleType.Both,
jsonHandleUrlKeys: new PathString[] { "/api" },
errorHandingPath: "/home/error")); app.UseAuthentication(); app.UseMvc();
} bool InBlacklist(string token)
{
//code: 实际项目中应该查询数据库或配置文件进行比对 return false;
} }
}

Startup.cs

Program.cs

 using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging; namespace Jwt.Gateway
{
public class Program
{
public static void Main(string[] args)
{
BuildWebHost(args).Run();
} public static IWebHost BuildWebHost(string[] args)
{
var config = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: true)
.Build(); return WebHost.CreateDefaultBuilder(args)
.UseKestrel()
.UseConfiguration(config)
.UseStartup<Startup>()
.Build();
}
}
}

Program.cs

运行截图

[运行截图-获取Token]

[运行截图-配置Fiddler调用接口获取数据]

[运行截图-获取到数据]

如果Token校验失败将会返回401错误!

如果你发现有错误,请善意指出,谢谢!

Asp.Net Core基于JWT认证的数据接口网关Demo的更多相关文章

  1. ASP.NET Core 基于JWT的认证(一)

    ASP.NET Core 基于JWT的认证(一) Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计 ...

  2. ASP.NET Core 基于JWT的认证(二)

    ASP.NET Core 基于JWT的认证(二) 上一节我们对 Jwt 的一些基础知识进行了一个简单的介绍,这一节我们将详细的讲解,本次我们将详细的介绍一下 Jwt在 .Net Core 上的实际运用 ...

  3. ASP.NET WebApi 基于JWT实现Token签名认证

    一.前言 明人不说暗话,跟着阿笨一起玩WebApi!开发提供数据的WebApi服务,最重要的是数据的安全性.那么对于我们来说,如何确保数据的安全将会是需要思考的问题.在ASP.NET WebServi ...

  4. asp.net core 集成JWT(二)token的强制失效,基于策略模式细化api权限

    [前言] 上一篇我们介绍了什么是JWT,以及如何在asp.net core api项目中集成JWT权限认证.传送门:https://www.cnblogs.com/7tiny/p/11012035.h ...

  5. ASP.NET Core的JWT的实现(中间件).md

    既然选择了远方,便只顾风雨兼程 __ HANS许 JWT(JSON Web Token) ASP.NET Core 的Middleware实现 引言:挺久没更新了,之前做了Vue的系列,后面想做做服务 ...

  6. asp.net core 集成JWT(一)

    [什么是JWT] JSON Web Token(JWT)是目前最流行的跨域身份验证解决方案. JWT的官网地址:https://jwt.io/ 通俗地来讲,JWT是能代表用户身份的令牌,可以使用JWT ...

  7. ASP.NET Core的身份认证框架IdentityServer4--入门

    ASP.NET Core的身份认证框架IdentityServer4--入门 2018年08月11日 10:09:00 qq_42606051 阅读数 4002   https://blog.csdn ...

  8. ASP.NET Core基于K8S的微服务电商案例实践--学习笔记

    摘要 一个完整的电商项目微服务的实践过程,从选型.业务设计.架构设计到开发过程管理.以及上线运维的完整过程总结与剖析. 讲师介绍 产品需求介绍 纯线上商城 线上线下一体化 跨行业 跨商业模式 从0开始 ...

  9. Dotnet core使用JWT认证授权最佳实践(二)

    最近,团队的小伙伴们在做项目时,需要用到JWT认证.遂根据自己的经验,整理成了这篇文章,用来帮助理清JWT认证的原理和代码编写操作. 第一部分:Dotnet core使用JWT认证授权最佳实践(一) ...

随机推荐

  1. Ping 命令实战小结--TCP/IP协议学习

    2011-12-22 22:38:49 图1 图2 一,环境说明 硬件连线.PC与2440开发板直接用网线连接. PC的ip地址:192.168.0.107.2440开发板的ip地址:192.168. ...

  2. linux 命令中英文对照,收集

    linux 命令中英文对照,收集   linux 命令英文全文 Is Linux CLI case-sensitive? The answer is, yes. If you try to run L ...

  3. GoldenGate HANDLECOLLISIONS参数使用说明

    HANDLECOLLISIONS在官方文档上的说明: 使用HANDLECOLLISIONS和NOHANDLECOLLISIONS参数来控制在目标上应用SQL时,Replicat是否尝试解决重复记录和缺 ...

  4. Github怎么写README

    编辑README文件 大标题(一级标题):在文本下面加等于号,那么上方的文字就变成了大标题,等于号的个数无限制,但一定要大于0 大标题 ==== 中标题(二级标题):在文本下面加下划线,那么上方的文本 ...

  5. .NET Framework简介

    NET Framework 就是微软Web Services 引擎1.NET Framework 旨在实现下列目标:提供一个一致的面向对象的编程环境,而无论对象代码是在本地存储和执行,还是在本地执行但 ...

  6. 前后端通信中使用Ajax与后台接口api交互(以登录功能为例)

    一.查阅开发文档 首先,要做这个功能前,我们必须先查阅后台接口文档,了解使用登录接口时,需要提交哪些参数,并且接口使用返回的数据. 这里我使用了一个返回json格式数据的登录接口为例,讲解怎么使用Aj ...

  7. win7有多条隧道适配器(isatap、teredo、6to4)的原因及关闭方法

    问题:sdp协商时,带有IPV6的信息,需要将IPV6相关信息去掉 原因:网卡启用了ipv6通道 解决:关闭IPv6数据接口 netsh interface isatap set state disa ...

  8. 面试官问我“Java中的锁有哪些?以及区别”,我跪了

    在读很多并发文章中,会提及各种各样锁如公平锁,乐观锁等等,这篇文章介绍各种锁的分类.介绍的内容如下: 公平锁/非公平锁 可重入锁 独享锁/共享锁 互斥锁/读写锁 乐观锁/悲观锁 分段锁 偏向锁/轻量级 ...

  9. JavaWeb三大组件

    一.JavaWeb三大组件 Servlet,Listener,Filter.它们在JavaWeb开发中分别提供不同的功能. JavaWeb三大组件都必须在Web.xml中配置 二.三大组件 1.Ser ...

  10. js备战春招の四のDOM

    通过js查找html元素的三种方法: 1.通过id找到html元素. 2.通过标签名找到html元素. 3.通过类名找到html元素. DOM HTML document.write(); 直接写入h ...