近日,应一位朋友的邀请写了个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. [Offer收割] 编程练习赛1

    A HihoCoder 1268 九宫 思路: 一般类似于数独的题目都是使用回溯解决,这里由于题目数据较小同样可以直接DFS得出结果.这里我用了一个偷懒的方法(next_permutation),直接 ...

  2. Python3 tkinter基础 Scrollbar pack 创建靠右、充满Y轴的垂直滚动条

             Python : 3.7.0          OS : Ubuntu 18.04.1 LTS         IDE : PyCharm 2018.2.4       Conda ...

  3. mongodb常用查询语句

    1.查询所有记录db.userInfo.find();相当于:select* from userInfo; 2.查询去掉后的当前聚集集合中的某列的重复数据db.userInfo.distinct(&q ...

  4. git push时报错:Updates were rejected because the tip of your current branch is behind

    出现这样的问题是由于:自己当前版本低于远程仓库版本 有如下几种解决方法: 1.使用强制push的方法: git push -u origin master -f 这样会使远程修改丢失,一般是不可取的, ...

  5. unity - ios打包真机测试及模拟器测试

    本文介绍unity游戏如何在iphone机和ios模拟器上进行测试打包 准备工作:mac电脑一台.iphone一部.连接mac和iPhone的数据线.unity.Xcode.苹果开发者账号. 苹果开发 ...

  6. 给video添加自定义进度条

    思路: 1.进度条,首先要知道视频的总长度,和视频的当前进度,与其对应的便是进度条的总长度和当前的长度,两者比值相等 2.获取视频的总长度(单位是秒),获取当前进度 3.要实现的功能,首先是进度条根据 ...

  7. 为wordpress后台登陆添加算术验证码

    对于新建站(个人博客-柠檬https://ninmong.com)的站长来说提高后台的安全性,是一件非常重要的事,添加验证可以起到很好的效果,废话少说,贴代码 //后台登陆数学验证码 function ...

  8. Java并发编程的挑战

    并发编程的目的是为了让程序运行得更快,但是,并不是线程启动的越多,就能让程序最大限度地并发执行.并发编程时,会面临非常多的挑战,比如上下文切换的问题,死锁的问题,以及受限于各种硬件和软件的资源限制问题 ...

  9. 添加“Git Bash Here”到右键菜单

    1.按键盘上的组合键[Win+R]把运行调出来 2.在运行中输入[regedit]再点击确定. 3.定位到HKEY_CLASSES_ROOT\Directory\Background\shell(如果 ...

  10. vim实现实时自动保存

    进https://www.vim.org/scripts/script.php?script_id=4521网站下载vim -auto-save wget  https://www.vim.org/s ...