知识全聚集 .Net Core 技术突破 | 如何实现一个模块化方案一 - 初久的私房菜 - 博客园

mikel阅读(661)

来源: 知识全聚集 .Net Core 技术突破 | 如何实现一个模块化方案一 – 初久的私房菜 – 博客园

教程

01 | 模块化方案一

02 | 模块化方案二

其他教程预览

分库分表项目实战教程

Git地址: https://github.com/MrChuJiu/EasyLogger

01 | 前言

02 | 简单的分库分表设计

03 | 控制反转搭配简单业务

04 | 强化设计方案

05 | 完善业务自动创建数据库

06 | 最终篇-通过AOP自动连接数据库-完成日志业务

简介

模块化的介绍一共2篇

这一篇我们实现一个功能非常简单的StartupModules模块化。

第二篇我们来实现一个ABP的模块化效果。

思考

其实来简单想一下模块化的实验思路,写个接口=>模块类继承该接口=>项目启动反射检索=>调用接口实现。
那么具体到代码实践应该怎么写呢。

开始

第一步

第一步就是写一个模块化接口类的嘛!
新建类 IStartupModule

然后写一个反射检索全文谁继承了这个接口的方法
新建类 StartupModulesOptions


代码解释: Activator.CreateInstance 与指定参数匹配程度最高的构造函数来创建指定类型的实例
ps:白话文就是,你给我Type我给你创建个对应的实例
更一个有意思的是 Assembly.GetEntryAssembly()! 这个! 是不是很好奇怕
ps:我第一次看到这个语法也蒙了,问了好多人大家都没用过,这个语法同TS中的断言,是非null类型断言,意思就是我断言我这个方法返回的内容绝对不是null。

第二步

到这里来看我们是不是已经拿到了所有继承接口的模块那么怎么在该调用的地方调用呢,缺啥写啥

第三步

运行代码也有了,我该怎么调用呢。
接下来只要在 在Program 的 WebHost 调用.UseStartupModules() 流程就可以加载我们的 ConfigureServices 了

中间来插播一下

请让我掏出来一个器大的东西来说 他就是: IStartupFilter ,这个东西是个啥东西呢。来看一段源码

namespace Microsoft.AspNetCore.Hosting
{
    public interface IStartupFilter
    {
        Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next);
    }
}

这里我们看到了 Configure 他返回一个 IApplicationBuilder 他是怎么用的呢
我们新建一个空的Web项目的时候不知道有没有注意过 UseStaticFiles 这个函数

 public void Configure(IApplicationBuilder app)
        {
            app.UseStaticFiles();
            app.UseRouting();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}");
            });
        }

在这个方法中,你可以直接使用方法提供的IApplicationBuilder参数,并且可以向其中添加各种中间件。使用IStartupFilter, 你可以指定并返回一个Action类型的泛型委托,这意味你除了可以使用方法提供的泛型委托配置IApplicationBuilder对象, 还需要返回一个泛型委托。

我们来看一段代码 Build(); 这个会调用BuildApplication方法

public class Program
{
    public static void Main(string[] args)
    {
        var host = new WebHostBuilder()
            .UseKestrel()    
            .UseContentRoot(Directory.GetCurrentDirectory())
            .UseStartup<Startup>()
            .Build();

        host.Run(); 
    }
}

private RequestDelegate BuildApplication()
{
    ..
    IApplicationBuilder builder = builderFactory.CreateBuilder(Server.Features);
    builder.ApplicationServices = _applicationServices;

    var startupFilters = _applicationServices.GetService<IEnumerable<IStartupFilter>>();
    Action<IApplicationBuilder> configure = _startup.Configure;
    foreach (var filter in startupFilters.Reverse())
    {
        configure = filter.Configure(configure);
    }

    configure(builder);

    return builder.Build();
}

首先,此方法创建IApplicationBuilder的实例,该实例将用于构建中间件管道,并将ApplicationServices设置为已配置的DI容器。
接下来的代码块很意思。首先,从DI容器中获取了一个集合IEnumerable<IStartupFilter>
我们可以配置多个IStartupFilter来形成一个管道,所以这个方法只是从容器中取出它们。
现在我们通过循环遍历每个IStartupFilter(以相反的顺序),传入Startup.Configure方法,然后更新局部变量configure来创建Configure方法的管道。

第四步

我们自己如何来实现 一个IStartupFilter 让他帮我们调用 Configure。

第五步

在 WebHostBuilderExtensions类 UseStartupModules 方法 ConfigureServices 下用 IStartupFilter 注入实现
这样在Build() 的时候就会调用模块的方法了

ActivatorUtilities.CreateInstance<ModulesStartupFilter>(sp, runner) // 第二个参数是在创建实例的时候 给构造函数注入的第一个参数

测试一下

新建 Core Web项目 在 Program.cs
 Host.CreateDefaultBuilder(args)
 .ConfigureWebHostDefaults(webBuilder =>
    {
         // 进行模块映射
         webBuilder.UseStartupModules().UseStartup<Startup>();
});

在 Startup.cs ConfigureServices和Configure 下打一个 Console.WriteLine

新建 类 HangfireStartupModule 继承 IStartupModule
public class HangfireStartupModule : IStartupModule
{
        public void ConfigureServices(IServiceCollection services)
        {
            Console.WriteLine("HangfireStartupModule----ConfigureServices");
        }
        public void Configure(IApplicationBuilder app)
        {
            Console.WriteLine("HangfireStartupModule----Configure");
        }
}
结果

一个非常简单的模块化就完工了,当然这个是基础版本,只能拿来借鉴思路学习下。

补充模块的 ConfigureServices 和 Configure 传递上下文

新建类 ConfigureServicesContext 和 ConfigureMiddlewareContext

  public class ConfigureMiddlewareContext
    {
        public ConfigureMiddlewareContext(IConfiguration configuration, IWebHostEnvironment hostingEnvironment, IServiceProvider serviceProvider, StartupModulesOptions options)
        {
            Configuration = configuration;
            HostingEnvironment = hostingEnvironment;
            ServiceProvider = serviceProvider;
            Options = options;
        }

        public IConfiguration Configuration { get; }

        public IWebHostEnvironment HostingEnvironment { get; }
        public IServiceProvider ServiceProvider { get; }

        public StartupModulesOptions Options { get; }
    }

    public class ConfigureServicesContext
    {
        public ConfigureServicesContext(IConfiguration configuration, IWebHostEnvironment hostingEnvironment, StartupModulesOptions options)
        {
            Configuration = configuration;
            HostingEnvironment = hostingEnvironment;
            Options = options;
        }

        public IConfiguration Configuration { get; }
        public IWebHostEnvironment HostingEnvironment { get; }
        public StartupModulesOptions Options { get; }
    }

修改 StartupModuleRunner 的方法

 public void ConfigureServices(IServiceCollection services, IConfiguration configuration, IWebHostEnvironment hostingEnvironment)
        {
            var ctx = new ConfigureServicesContext(configuration, hostingEnvironment, _options);
            foreach (var cfg in _options.StartupModules)
            {
                cfg.ConfigureServices(services, ctx);
            }
        }

        public void Configure(IApplicationBuilder app, IConfiguration configuration, IWebHostEnvironment hostingEnvironment)
        {
            using (var scope = app.ApplicationServices.CreateScope()) {
                var ctx = new ConfigureMiddlewareContext(configuration, hostingEnvironment, scope.ServiceProvider, _options);

                foreach (var cfg in _options.StartupModules)
                {
                    cfg.Configure(app, ctx);
                }
            }
               
        }

鸣谢

玩双截棍的熊猫、NETCore-大黄瓜

思路来源:https://github.com/henkmollema/StartupModules

友联: https://github.com/DestinyCore/Destiny.Core.Flow

smarty模板强制编译_翔宇的博客-CSDN博客

mikel阅读(754)

来源: smarty模板强制编译_翔宇的博客-CSDN博客

开发过程中碰到一个奇怪的现象,每次写完模板之后总是没有更新,一番搜索之后发现是smarty的缓存,并且项目的smarty配置被人修改过,囧。

参考这篇文章,解释下其中这两个参数:

/**
* force template compiling?
* @var boolean
*/
public $force_compile = false;
/**
* check template for modifications?
* @var boolean
*/
public $compile_check = true;
/**
* use sub dirs for compiled/cached files?
* @var boolean
*/

上面是smarty的默认配置
– $force_compile为true时每次用户访问页面都会重新编译模板,应设为false,减少io
– $compile_check为true时smarty会检查模板文件的变更,有变更会重新编译,应设为true

被人设置为false false了 =。= ,改回来就可以了

一款基于.NET Core的认证授权解决方案-葫芦藤1.0开源啦 - 福禄网络技术团队 - 博客园

mikel阅读(740)

来源: 一款基于.NET Core的认证授权解决方案-葫芦藤1.0开源啦 – 福禄网络技术团队 – 博客园

背景

18年公司准备在技术上进行转型,而公司技术团队是互相独立的,新技术的推动阻力很大。我们需要找到一个切入点。公司的项目很多,而各个系统之间又不互通,导致每套系统都有一套登录体系,给员工和客户都带来极大的不便。那么从登录切入进去无疑最合适,对于各个团队的技术改造成本也不大。所以我们团队第一个项目就是搭建一套统一登录认证授权系统,那么葫芦藤项目应运而生。

技术方案

后端框架:.NET Core3.1(后期会推出 .NET 5版本)

前端框架:React

数据库:mySQL(可根据实际情况,自由切换)

中间件:redis

详细功能

认证授权服务

基于IdentityServer4实现的协议,支持网站、本地应用、移动端、web服务等应用的认证授权逻辑。

单点登录登出

支持各种类型应用上的单点登录登出。开箱即用的基础用户管理模块,包括:注册、登录、手机验证码、忘记密码等。为了安全考虑,集成了腾讯图形验证码。

第三方登录(微信、钉钉)

完善的第三方登录支持。支持首次登录时绑定已存在用户或注册新用户后,自动绑定。

如何快速使用

1.下载代码

clone代码到本地。根目录结构如下:

20201103153907

其中,backend存放的是后端代码,frontend存放的是前端代码。

进入backend目录,使用Visual Studio打开解决方案。目录结构如下:

20201103154250

2.生成数据库

首先在Fulu.Passport.Web中找到appsettings.Development.json文件。编辑数据库连接字符串:

20201103155350

打开程序包管理器,切换默认项目为:Fulu.Passport.Web, 如下图所示:

20201106111334

然后在程序包管理器中执行如下命令:


Add-Migration Init

最后执行完成后,再执行如下命令:

update-database

执行完以上操作后,如没有报错,则会创建数据库,并会在Client表中创建一条测试数据,如下图所示:

20201103160408

3.按F5启动后端服务

注:由于项目中依赖redis来处理缓存,所以正式启动之前,需要将appsettings.Development.json文件里的redis配置改为你自己的。

4.启动前端

切换目录到frontend,在命令行中执行如下命令:

npm install

执行完毕后,执行如下命令:

npm run demo

执行结果如下图所示:

20201103161300

然后通过http://localhost:8080进行访问。界面如下所示:

20201103174200

至此,前后端服务已启动完毕,一个开箱即用的认证授权服务就完成了。

5.新客户端如何快速接入认证服务?

认证授权服务存在的意义就是提供统一的认证授权入口,有了这个服务后,每个新的客户端应用无需单独开发认证授权模块。下面就来一起看下如何快速将新应用接入到认证授权服务。(此处以 ASP.NET Core作为示例,其他语言大同小异)。

示例代码在sample文件夹中,如下图所示:

20201104165955

在正式接入之前,必须先申请应用。(此版本未提供应用管理服务)通过在数据库中添加示例信息,如下图所示:

20201104192124

示例SQL脚本:

INSERT INTO `fulusso`.`client`(`client_secret`, `full_name`, `host_url`, `redirect_uri`, `description`, `enabled`, `id`) VALUES ('14p9ao1gxu4q3sp8ogk8bq4gkct59t9w', '葫芦藤2', 'http://localhost:5003/', 'http://localhost:5003', NULL, 1, UUID());

其中,redirect_uri参数指的是从认证服务获取code之后,重定向的url。为了开发的方便,我们的认证服务中仅校验回调域名的域名,不会校验完整的地址。比如,你的redirect_uri为http://www.xxx.com/abc/aaa,则数据库中的redirect_uri字段填写http://www.xxx.com即可。

应用信息导入到数据库后,在Startup类的ConfigureServices方法中,添加如下代码:

services.AddServiceAuthorize(o =>
{
    o.AllowClientToken = true;
    o.AllowUserToken = true;
    o.OnClientValidate = false;
    o.Authority = "http://localhost:5000";
    o.ValidateAudience = false;
    o.ClientId = Configuration["AppSettings:ClientId"];
    o.ClientSecret = Configuration["AppSettings:ClientSecret"];
});

注:需添加Fulu.Service.Authorize项目引用,如下图所示:

20201104170401

然后在Configure方法中,添加如下代码:

 app.UseRouting();
 app.UseJwtAuthorize();
 app.UseAuthorization();

其中,UseJwtAuthorize是自定义的中间件,为了实现OAuth2.0的授权码的逻辑。
限于篇幅,具体代码不在此列出。可在代码仓库中查看。

到此为止,这个新应用就成功的接入到认证服务了。

当未登录的时候,访问此应用的页面会自动跳转到认证服务的login界面。登录之后,会重定向回登录之前的页面。如下图所示:

aa

下一版功能规划

1.更多的第三方平台的接入(QQ、微博等)

2.api授权服务

3.更安全的二次验证,集成google令牌

4.应用管理

等等~~~~,尽请期待。

体验

演示地址:https://account.suuyuu.cn/

代码仓库:https://github.com/fuluteam/fulusso

(3)ASP.NET Core3.1 Ocelot认证 - 暗断肠 - 博客园

mikel阅读(611)

来源: (3)ASP.NET Core3.1 Ocelot认证 – 暗断肠 – 博客园

.认证

当客户端通过Ocelot访问下游服务的时候,为了保护下游资源服务器会进行认证鉴权,这时候需要在Ocelot添加认证服务。添加认证服务后,随后使用Ocelot基于声明的任何功能,例如授权或使用Token中的值修改请求。用户必须像往常一样在其Startup.cs中注册身份验证服务,但是他们为每次注册提供一个方案(身份验证提供者密钥),例如:

复制代码
public void ConfigureServices(IServiceCollection services)
{
    var authenticationProviderKey = "TestKey";
    services.AddAuthentication()
        .AddJwtBearer(authenticationProviderKey, x =>
        {
        });
}
复制代码

在此Ocelot认证项目示例中,TestKey是已注册此提供程序的方案。然后我们将其映射到配置中的Routes路由,例如:

复制代码
{
    "Routes": [
        {
            "DownstreamPathTemplate": "/api/customers",
            "DownstreamScheme": "http",
            "DownstreamHost": "localhost",
            "DownstreamPort": 9001,
            "UpstreamPathTemplate": "/customers",
            "UpstreamHttpMethod": [ "Get" ],
            "AuthenticationOptions": {
                "AuthenticationProviderKey": "TestKey",
                "AllowedScopes": []
            }
        }
    ]
}
复制代码

Ocelot运行时,它将查看Routes.AuthenticationOptions.AuthenticationProviderKey并检查是否存在使用给定密钥注册的身份验证提供程序。如果不存在,则Ocelot将不会启动,如果存在,则Routes将在执行时使用该提供程序。
如果对路由进行身份验证,Ocelot将在执行身份验证中间件时调用与之关联的任何方案。如果请求通过身份验证失败,Ocelot将返回http状态代码401。

2.JWT Tokens Bearer认证

Json Web Token (JWT),是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准(RFC 7519)。该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。

2.1JWT令牌结构

在紧凑的形式中,JSON Web Tokens由dot(.)分隔的三个部分组成,它们是:
Header头、Payload有效载荷、Signature签名
因此,JWT通常如下所示:xxxxx.yyyyy.zzzzz(Header.Payload.Signature)

2.1.1Header头

标头通常由两部分组成:令牌的类型,即JWT,以及正在使用的签名算法,例如HMAC SHA256或RSA。例如:

{
  "alg": "HS256",
  "typ": "JWT"
}

然后,这个JSON被编码为Base64Url,形成JWT的第一部分。

2.1.2Payload有效载荷

Payload部分也是一个JSON对象,用来存放实际需要传递的数据。JWT规定了7个官方字段,供选用。
iss (issuer):签发人
exp (expiration time):过期时间
sub (subject):主题
aud (audience):受众
nbf (Not Before):生效时间
iat (Issued At):签发时间
jti (JWT ID):编号
除了官方字段,你还可以在这个部分定义私有字段,下面就是一个例子。例如:

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}

注意,JWT默认是不加密的,任何人都可以读到,所以不要把秘密信息放在这个部分。这个JSON对象也要使用Base64URL算法转成字符串。

2.1.3.Signature签名

Signature部分是对前两部分的签名,防止数据篡改。
首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用Header里面指定的签名算法(默认是HMAC SHA256),按照下面的公式产生签名。

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)

签名用于验证消息在此过程中未被更改,并且,在使用私钥签名的令牌的情况下,它还可以验证JWT的发件人是否是它所声称的人。
把他们三个全部放在一起,输出是三个由点分隔的Base64-URL字符串,可以在HTML和HTTP环境中轻松传递,而与基于XML的标准(如SAML)相比更加紧凑。
下面显示了一个JWT,它具有先前的头和有效负载编码,并使用机密签名。

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoid3prNzAzIiwibmJmIjoiMTU5MjE0MzkzNyIsImV4cCI6MTU5MjE0Mzk5OCwiaXNzIjoiYXV0aC5qd3QuY2MiLCJhdWQiOiJkZW5nd3V8MjAyMC82LzE0IDIyOjEyOjE5In0
.4RiwhRy0rQkZjclOFWyTpmW7v0AMaL3aeve1L-eWIz0

其实一般发送用户名和密码获取token那是由Identity4来完成的,包括验证用户,生成JwtToken。但是项目这里是由System.IdentityModel.Tokens类库来生成JwtToken。最后返回jwt令牌token给用户。JwtToken解码可以通过https://jwt.io/中进行查看。

3.项目演示

3.1APIGateway项目

在该项目中启用身份认证来保护下游api服务,使用JwtBearer认证,将默认的身份验证方案设置为TestKey。在appsettings.json文件中配置认证中密钥(Secret)跟受众(Aud)信息:

复制代码
{
    "Audience": {
        "Secret": "Y2F0Y2hlciUyMHdvbmclMjBsb3ZlJTIwLm5ldA==",
        "Iss": "http://www.c-sharpcorner.com/members/catcher-wong",
        "Aud": "Catcher Wong"
    }
}
复制代码

Startup添加身份认证代码如下:

复制代码
public void ConfigureServices(IServiceCollection services)
{
    //获取appsettings.json文件中配置认证中密钥(Secret)跟受众(Aud)信息
    var audienceConfig = Configuration.GetSection("Audience");
    //获取安全秘钥
    var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(audienceConfig["Secret"]));
    //token要验证的参数集合
    var tokenValidationParameters = new TokenValidationParameters
    {
        //必须验证安全秘钥
        ValidateIssuerSigningKey = true,
        //赋值安全秘钥
        IssuerSigningKey = signingKey,
        //必须验证签发人
        ValidateIssuer = true,
        //赋值签发人
        ValidIssuer = audienceConfig["Iss"],
        //必须验证受众
        ValidateAudience = true,
        //赋值受众
        ValidAudience = audienceConfig["Aud"],
        //是否验证Token有效期,使用当前时间与Token的Claims中的NotBefore和Expires对比
        ValidateLifetime = true,
        //允许的服务器时间偏移量
        ClockSkew = TimeSpan.Zero,
        //是否要求Token的Claims中必须包含Expires
        RequireExpirationTime = true,
    };
    //添加服务验证,方案为TestKey
    services.AddAuthentication(o =>
    {
        o.DefaultAuthenticateScheme = "TestKey";
    })
    .AddJwtBearer("TestKey", x =>
        {
            x.RequireHttpsMetadata = false;
            //在JwtBearerOptions配置中,IssuerSigningKey(签名秘钥)、ValidIssuer(Token颁发机构)、ValidAudience(颁发给谁)三个参数是必须的。
            x.TokenValidationParameters = tokenValidationParameters;
        });
    //添加Ocelot网关服务时,包括Secret秘钥、Iss签发人、Aud受众
    services.AddOcelot(Configuration);
}
public async void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    //使用认证服务
    app.UseAuthentication();
    //使用Ocelot中间件
    await app.UseOcelot();
}
复制代码
3.1.1Identity Server承载JWT Token

在第二小节介绍JWT Token认证时候,我们都知道一般发送用户名和密码获取Token那是由Identity4来完成的,包括验证用户,生成JWT Token。也就是说Identity Server承载了JWT Token认证功能。为了使用IdentityServer承载Token,请像往常一样在ConfigureServices中使用方案(密钥)注册IdentityServer服务。如果您不知道如何执行此操作,请查阅IdentityServer文档。

复制代码
public void ConfigureServices(IServiceCollection services)
{
    var authenticationProviderKey = "TestKey";
    Action<IdentityServerAuthenticationOptions> options = o =>
        {
            o.Authority = "https://whereyouridentityserverlives.com";
            o.ApiName = "api";
            o.SupportedTokens = SupportedTokens.Both;
            o.ApiSecret = "secret";
        };
    services.AddAuthentication()
        .AddIdentityServerAuthentication(authenticationProviderKey, options);
    services.AddOcelot();
}
复制代码

在Identity4中是由Authority参数指定OIDC服务地址,OIDC可以自动发现Issuer, IssuerSigningKey等配置,而o.Audience与x.TokenValidationParameters = new TokenValidationParameters { ValidAudience = “api” }是等效的。

3.2AuthServer项目

此服务主要用于客户端请求受保护的资源服务器时,认证后产生客户端需要的JWT Token,生成JWT Token关键代码如下:

复制代码
[Route("api/[controller]")]
public class AuthController : Controller
{
    private IOptions<Audience> _settings;
    public AuthController(IOptions<Audience> settings)
    {
        this._settings = settings;
    }
    /// <summary>
    ///用户使用 用户名密码 来请求服务器
    ///服务器进行验证用户的信息
    ///服务器通过验证发送给用户一个token
    ///客户端存储token,并在每次请求时附送上这个token值, headers: {'Authorization': 'Bearer ' + token}
    ///服务端验证token值,并返回数据
    /// </summary>
    /// <param name="name"></param>
    /// <param name="pwd"></param>
    /// <returns></returns>
    [HttpGet]
    public IActionResult Get(string name, string pwd)
    {
        //验证登录用户名和密码
        if (name == "catcher" && pwd == "123")
        {
            var now = DateTime.UtcNow;
            //添加用户的信息,转成一组声明,还可以写入更多用户信息声明
            var claims = new Claim[]
            {
                //声明主题
                new Claim(JwtRegisteredClaimNames.Sub, name),
                    //JWT ID 唯一标识符
                new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
                    //发布时间戳 issued timestamp
                new Claim(JwtRegisteredClaimNames.Iat, now.ToUniversalTime().ToString(), ClaimValueTypes.Integer64)
            };
            //下面使用 Microsoft.IdentityModel.Tokens帮助库下的类来创建JwtToken

            //安全秘钥
            var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(_settings.Value.Secret));

            //声明jwt验证参数
            var tokenValidationParameters = new TokenValidationParameters
            {
                //必须验证安全秘钥
                ValidateIssuerSigningKey = true,
                //赋值安全秘钥
                IssuerSigningKey = signingKey,
                //必须验证签发人
                ValidateIssuer = true,
                //赋值签发人
                ValidIssuer = _settings.Value.Iss,
                //必须验证受众
                ValidateAudience = true,
                //赋值受众
                ValidAudience = _settings.Value.Aud,
                //是否验证Token有效期,使用当前时间与Token的Claims中的NotBefore和Expires对比
                ValidateLifetime = true,
                //允许的服务器时间偏移量
                ClockSkew = TimeSpan.Zero,
                //是否要求Token的Claims中必须包含Expires
                RequireExpirationTime = true,
            };
            var jwt = new JwtSecurityToken(
                //jwt签发人
                issuer: _settings.Value.Iss,
                //jwt受众
                audience: _settings.Value.Aud,
                //jwt一组声明
                claims: claims,
                notBefore: now,
                //jwt令牌过期时间
                expires: now.Add(TimeSpan.FromMinutes(2)),
                //签名凭证: 安全密钥、签名算法
                signingCredentials: new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256)
            );
            //生成jwt令牌(json web token)
            var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);
            var responseJson = new
            {
                access_token = encodedJwt,
                expires_in = (int)TimeSpan.FromMinutes(2).TotalSeconds
            };
            return Json(responseJson);
        }
        else
        {
            return Json("");
        }
    }
}
public class Audience
{
    public string Secret { get; set; }
    public string Iss { get; set; }
    public string Aud { get; set; }
}
复制代码

appsettings.json文件中配置认证中密钥(Secret)跟受众(Aud)信息:

复制代码
{
    "Audience": {
        "Secret": "Y2F0Y2hlciUyMHdvbmclMjBsb3ZlJTIwLm5ldA==",
        "Iss": "http://www.c-sharpcorner.com/members/catcher-wong",
        "Aud": "Catcher Wong"
    }
}
复制代码

3.3CustomerAPIServices项目

该项目跟APIGateway项目是一样的,为了保护下游api服务,使用JwtBearer认证,将默认的身份验证方案设置为TestKey。在appsettings.json文件中配置认证中密钥(Secret)跟受众(Aud)信息:

复制代码
{
    "Audience": {
        "Secret": "Y2F0Y2hlciUyMHdvbmclMjBsb3ZlJTIwLm5ldA==",
        "Iss": "http://www.c-sharpcorner.com/members/catcher-wong",
        "Aud": "Catcher Wong"
    }
}
复制代码

Startup添加身份认证代码如下:

复制代码
public void ConfigureServices(IServiceCollection services)
{
    //获取appsettings.json文件中配置认证中密钥(Secret)跟受众(Aud)信息
    var audienceConfig = Configuration.GetSection("Audience");
    //获取安全秘钥
    var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(audienceConfig["Secret"]));
    //token要验证的参数集合
    var tokenValidationParameters = new TokenValidationParameters
    {
        //必须验证安全秘钥
        ValidateIssuerSigningKey = true,
        //赋值安全秘钥
        IssuerSigningKey = signingKey,
        //必须验证签发人
        ValidateIssuer = true,
        //赋值签发人
        ValidIssuer = audienceConfig["Iss"],
        //必须验证受众
        ValidateAudience = true,
        //赋值受众
        ValidAudience = audienceConfig["Aud"],
        //是否验证Token有效期,使用当前时间与Token的Claims中的NotBefore和Expires对比
        ValidateLifetime = true,
        //允许的服务器时间偏移量
        ClockSkew = TimeSpan.Zero,
        //是否要求Token的Claims中必须包含Expires
        RequireExpirationTime = true,
    };
    //添加服务验证,方案为TestKey
    services.AddAuthentication(o =>
    {
        o.DefaultAuthenticateScheme = "TestKey";
    })
    .AddJwtBearer("TestKey", x =>
        {
            x.RequireHttpsMetadata = false;
            //在JwtBearerOptions配置中,IssuerSigningKey(签名秘钥)、ValidIssuer(Token颁发机构)、ValidAudience(颁发给谁)三个参数是必须的。
            x.TokenValidationParameters = tokenValidationParameters;
        });

    services.AddMvc();
}
public void Configure(IApplicationBuilder app)
{
    //使用认证服务
    app.UseAuthentication();
    app.UseMvc();
}
复制代码

在CustomersController下添加一个需要认证方法,一个不需要认证方法:

复制代码
[Route("api/[controller]")]
public class CustomersController : Controller
{
    //添加认证属性
    [Authorize]
    [HttpGet]
    public IEnumerable<string> Get()
    {
        return new string[] { "Catcher Wong", "James Li" };
    }
    [HttpGet("{id}")]
    public string Get(int id)
    {
        return $"Catcher Wong - {id}";
    }
}
复制代码

3.4ClientApp项目

该项目是用来模拟客户端访问资源服务器整个认证流程测试项目,在Program主程序可以看到如下代码:

复制代码
class Program
{
    static void Main(string[] args)
    {
        HttpClient client = new HttpClient();

        client.DefaultRequestHeaders.Clear();
        client.BaseAddress = new Uri("http://localhost:9000");

        // 1. without access_token will not access the service
        //    and return 401 .
        var resWithoutToken = client.GetAsync("/customers").Result;

        Console.WriteLine($"Sending Request to /customers , without token.");
        Console.WriteLine($"Result : {resWithoutToken.StatusCode}");

        //2. with access_token will access the service
        //   and return result.
        client.DefaultRequestHeaders.Clear();
        Console.WriteLine("\nBegin Auth....");
        var jwt = GetJwt();
        Console.WriteLine("End Auth....");
        Console.WriteLine($"\nToken={jwt}");

        client.DefaultRequestHeaders.Add("Authorization", $"Bearer {jwt}");
        var resWithToken = client.GetAsync("/customers").Result;

        Console.WriteLine($"\nSend Request to /customers , with token.");
        Console.WriteLine($"Result : {resWithToken.StatusCode}");
        Console.WriteLine(resWithToken.Content.ReadAsStringAsync().Result);

        //3. visit no auth service
        Console.WriteLine("\nNo Auth Service Here ");
        client.DefaultRequestHeaders.Clear();
        var res = client.GetAsync("/customers/1").Result;

        Console.WriteLine($"Send Request to /customers/1");
        Console.WriteLine($"Result : {res.StatusCode}");
        Console.WriteLine(res.Content.ReadAsStringAsync().Result);

        Console.Read();
    }
    private static string GetJwt()
    {
        HttpClient client = new HttpClient();

        client.BaseAddress = new Uri( "http://localhost:9000");
        client.DefaultRequestHeaders.Clear();

        var res2 = client.GetAsync("/api/auth?name=catcher&pwd=123").Result;

        dynamic jwt = JsonConvert.DeserializeObject(res2.Content.ReadAsStringAsync().Result);

        return jwt.access_token;
    }
}
复制代码

运行项目看看测试结果:

结合代码,我们能看到当客户端通过Ocelot网关访问下游服务http://localhost:9000/api/Customers/Get方法时候,因为该方法是需要通过认证才返回处理结果的,所以会进行JWT Token认证,如果发现没有Token,Ocelot则返回http状态代码401拒绝访问。如果我们通过GetJwt方法在AuthServer服务上登录认证获取到授权Token,然后再访问该资源服务器接口,立即就会返回处理结果,通过跟而未加认证属性的http://localhost:9000/api/Customers/Get/{id}方法对比,我们就知道,Ocelot认证已经成功了!

4.总结

该章节只是结合demo项目简单介绍在Ocelot中如何使用JWT Token认证。其实正式环境中,Ocelot是应该集成IdentityServer认证授权的,同样的通过重写Ocelot中间件我们还可以把configuration.json的配置信息存储到数据库或者缓存到Redis中。

参考文献:
Ocelot官网

增势税票识别助手,可识别照片、扫描件、电子票、形成电子台帐。 - 一壶茶水 - 博客园

mikel阅读(995)

来源: 增势税票识别助手,可识别照片、扫描件、电子票、形成电子台帐。 – 一壶茶水 – 博客园

 

该助手可以快速、准确的识别并读取发票相关字段信息,与传统的人工录入核对的方式相比,时效性更高,数据准确性更强。

同时,软件具备批量导出功能,数据读取字段与公司综项系统相一致,可以实现快捷的导入到综项系统中,大大减少操作人员的工作量。

首先本小工具使用C# winfrom 实现,其中主要是使用了百度智能云OCR文字识别技术,调用期官网接口,很简单,搭配NPOI Execl操作类库,

利用Spire.pdf类库,把pdf格式发票,转换为png图片格式。自动识别图片、pdf格式发票,发票可以用高拍仪、手机拍照、扫面件等都可以识别。

  其他说明:本程序借助百度智能云API作为基础的发票识别技术,识别准确率在98%以上,同时,由于百度智能云API每天免费授权识别限制在500次/天且可以对个人用户申请开放。

在授权管理中个人可以根据使用量的多少自行申请百度智能云API认证授权API key和Secret key,然后在发票识别助手中认证。

发票识别助手共分5个功能模块,操作相对很简单,第一步点击添加发票按钮,选择要识别的发票信息。注意说明:目前图片格式支持jpg、png、bmp,图片的长和宽要求最短边大于10px,

最长边小于2048px;图像编码后大小必须小于4M,建议不要超过1M;第二步点击识别发票按钮,系统开始识别发票信息,识别完成后,发票信息会自动生成;

 

介绍一下关键的代码:

一、获取百度云API token,这个是官方给的,直接拿过来用就可以了。

二、增值税票识别请求过程和参数传递,也是官方给的例子,自己按照需求修改一下就可以了。

 三、这里的部分是把pdf格式的发票,自动转换为png格式,提供出百度云api需要的文件格式。

 四、获取api返回的数据,输出到dataGridView中。

五、导出发票明细到EXECL表格中。

操作说明如下:

事务(进程 ID 51)与另一个进程被死锁在 锁 资源上,并且已被选作死锁牺牲品_小白博客-CSDN博客

mikel阅读(1274)

来源: 事务(进程 ID 51)与另一个进程被死锁在 锁 资源上,并且已被选作死锁牺牲品_小白博客-CSDN博客

所有死锁的原因可归结为资源的竞争
表现一:

一个用户A 访问表A(锁住了表A),然后又访问表B 另一个用户B 访问表B(锁住了表B),然后企图访问表A 这时用户A由于用户B已经锁住表B,它必须等待用户B释放表B,才能继续,好了他老人家就只好老老实实在这等了 同样用户B要等用户A释放表A才能继续这就死锁了

解决方法:
    这种死锁是由于你的程序的'bug'产生的,除了调整你的程序的逻辑别无他法,仔细分析你程序的逻辑,
    1:尽量避免同时锁定两个资源
    2: 必须同时锁定两个资源时,要保证在任何时刻都应该按照相同的顺序来锁定资源.
  • 1
  • 2
  • 3
  • 4
表现二:

用户A读一条纪录,然后修改该条纪录,这是用户B修改该条纪录这里用户A的事务里锁的性质由共享锁企图上升到独占锁(forupdate),而用户B里的独占锁由于A有共享锁存在所以必须等A释放掉共享锁,而A由于B的独占锁而无法上升的独占锁也就不可能释放共享锁,于是出现了死锁

解决方法:
    让用户A的事务(即先读后写类型的操作),在select 时就是用Update lock
    语法如下:
    select * from table1 (updlock) where ....
  • 1
  • 2
  • 3
  • 4

原文地址


NOLOCK

NOLOCK在概念上类似于READ UNCOMMITTED隔离级别,并且只针对于SELECT查询语句,它不会获取表的共享锁,换句话说不会阻止排它锁来更新数据行。当我们对表进行NOLOCK有什么好处呢?它能够提高并发性能,因为此时SQL Server数据库引擎不必去维护共享锁,由于不会对正在读取的表获取共享锁,所以可能导致未提交的事务也会被读取,所以此时缺点显而易见将导致脏读

SELECT COUNT(*) FROM Example WITH(NOLOCK)
  • 1
READPAST

当在表中用READPAST指定提示时此时SQL Server数据库引擎在返回结果集时将不会返回锁定的行或者数据页。它除了和NOLOCK一样不会导致查询阻塞外,因为不会返回锁定的行记录所以其优点好包括不存在脏读。但是其缺点则是因为不包含锁定的行记录但是很难保证结果集或者修改语句是否包含我们所必须需要返回的行。有可能在我们的业务逻辑中,需要返回我们必须需要的行。它的使用方式和NOLOCK一样

SELECT COUNT(*)FROM Example WITH(READPAST)
  • 1
UPDLOCK

UPDLOCK只是针对于表中的某一行记录来锁定从而阻止其他操作对该行的数据更新,说到这里想必我们已经明了,UPDLOCK是行级别,而排它锁则是表级别,二者不可同日而语。也就说当我们对某一行添加UPDLOCK提示时并不会阻塞其他查询操作

BEGIN TRAN
 select * from Example WITH (UPDLOCK) where SaleID = 1
此时我们再来开一个窗口进行查询,如下:
select * from Example
  • 1
  • 2
  • 3
  • 4
HOLDLOCK

使用HOLDLOCK提示时,此时查询将锁定表且被强制序列化,直到事务完成,才会被释放,其类似于SERIALIZABLE最高隔离级别

BEGIN TRAN
 select * from Example WITH (UPDLOCK,HOLDLOCK) where SaleID = 1
  • 1
  • 2

原文地址

 C#中List集合使用OrderByDescending方法对集合进行倒序排序 -

mikel阅读(2392)

来源: 【转载】 C#中List集合使用OrderByDescending方法对集合进行倒序排序 – 江湖逍遥 – 博客园

C#的List集合操作中,有时候需要针对List集合进行排序操作,如果是对List集合按照元素对象或者元素对象的某个属性进行倒序排序的话,可以使用OrderByDescending方法来实现,OrderByDescending方法属于List集合的扩展方法,方法的调用形式为使用Lambda表达式语句。

(1)对List<int>集合对象list1进行从大到小降序排序可使用下列语句:

List<int> list1 = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
list1 = list1.OrderByDescending(t => t).ToList();

(2)按List集合中的元素对象的某个具体属性进行倒序排序也可使用OrderByDescending方法。

我们需要对List<TestModel>集合对象testList进行排序,排序规则为按照对象属性Index降序排序。

首先看下TestModel的定义:

public class TestModel
{
public int Index { set; get; }

public string Name { set; get; }
}

对testList集合按元素对象的Index属性进行倒序排序可使用下列语句:

List<TestModel> testList = new List<ConsoleApplication1.TestModel>();
testList = testList.OrderByDescending(t => t.Index).ToList();

Web API 强势入门指南 - 微软互联网开发支持 - 博客园

mikel阅读(825)

来源: Web API 强势入门指南 – 微软互联网开发支持 – 博客园

Web API是一个比较宽泛的概念。这里我们提到Web API特指ASP.NET Web API。

这篇文章中我们主要介绍Web API的主要功能以及与其他同类型框架的对比,最后通过一些相对复杂的实例展示如何通过Web API构建http服务,同时也展示了Visual Studio构建.net项目的各种强大。

目录

什么是 Web API

官方定义如下,强调两个关键点,即可以对接各种客户端(浏览器,移动设备),构建http服务的框架。

ASP.NET Web API is a framework that makes it easy to build HTTP services that reach a broad range of clients, including browsers and mobile devices. ASP.NET Web API is an ideal platform for building RESTful applications on the .NET Framework.

Web API在ASP.NET完整框架中地位如下图,与SignalR一起同为构建Service的框架。Web API负责构建http常规服务,而SingalR主要负责的是构建实时服务,例如股票,聊天室,在线游戏等实时性要求比较高的服务。

Picture20

 

为什么要用 Web API

Web API最重要的是可以构建面向各种客户端的服务。另外与WCF REST Service不同在于,Web API利用Http协议的各个方面来表达服务(例如 URI/request response header/caching/versioning/content format),因此就省掉很多配置。

Picture2

 

当你遇到以下这些情况的时候,就可以考虑使用Web API了。

  • 需要Web Service但是不需要SOAP
  • 需要在已有的WCF服务基础上建立non-soap-based http服务
  • 只想发布一些简单的Http服务,不想使用相对复杂的WCF配置
  • 发布的服务可能会被带宽受限的设备访问
  • 希望使用开源框架,关键时候可以自己调试或者自定义一下框架

功能简介

Web API的主要功能

1. 支持基于Http verb (GET, POST, PUT, DELETE)的CRUD (create, retrieve, update, delete)操作

通过不同的http动作表达不同的含义,这样就不需要暴露多个API来支持这些基本操作。

2. 请求的回复通过Http Status Code表达不同含义,并且客户端可以通过Accept header来与服务器协商格式,例如你希望服务器返回JSON格式还是XML格式。

3. 请求的回复格式支持 JSON,XML,并且可以扩展添加其他格式。

4. 原生支持OData

5. 支持Self-host或者IIS host。

6. 支持大多数MVC功能,例如Routing/Controller/Action Result/Filter/Model Builder/IOC Container/Dependency Injection。

Web API vs MVC

你可能会觉得Web API 与MVC很类似,他们有哪些不同之处呢?先上图,这就是他们最大的不同之处。

Picture1

详细点说他们的区别,

  • MVC主要用来构建网站,既关心数据也关心页面展示,而Web API只关注数据
  • Web API支持格式协商,客户端可以通过Accept header通知服务器期望的格式
  • Web API支持Self Host,MVC目前不支持
  • Web API通过不同的http verb表达不同的动作(CRUD),MVC则通过Action名字表达动作
  • Web API内建于ASP.NET System.Web.Http命名空间下,MVC位于System.Web.Mvc命名空间下,因此model binding/filter/routing等功能有所不同
  • 最后,Web API非常适合构建移动客户端服务

Web API vs WCF

发布服务在Web API和WCF之间该如何取舍呢?这里提供些简单地判断规则,

  • 如果服务需要支持One Way Messaging/Message Queue/Duplex Communication,选择WCF
  • 如果服务需要在TCP/Named Pipes/UDP (wcf 4.5),选择WCF
  • 如果服务需要在http协议上,并且希望利用http协议的各种功能,选择Web API
  • 如果服务需要被各种客户端(特别是移动客户端)调用,选择Web API

Web API 实战 (Web API + MongoDB + knockoutjs)

ASP.NET网站上有很多简单的Web API实例,看看贴图和实例代码你就明白怎么用了。这里我们通过一个稍微复杂一点的实例来展示下Web API的功能。

涉及技术

在我们的实例里面用到了:

服务URI Pattern

Action Http verb URI
Get contact list GET /api/contacts
Get filtered contacts GET /api/contacts?$top=2
Get contact by ID GET /api/contacts/id
Create new contact POST /api/contacts
Update a contact PUT /api/contacts/id
Delete a contact DELETE /api/contacts/id

准备工作

1. 下载并安装Mongo DB,步骤看这里

2. Mongo DB C# driver下载可以在nuget搜索mongocsharpdriver。

3. 如果想本地察看数据库中内容,下载MongoVUE

4. Knockoutjs下载可以在nuget搜索knockoutjs。

代码实现

1. 创建项目

创建MVC4 Web Application

1

在Project Template中选择Web API

2

然后项目就创建成了,Controllers里面有一个ValuesController,是自动生成的一个最简单的Web API Controller。

正如我们前面所说,里面引用的是System.Web.Http命名空间。

3

2. 创建model

在model里面添加Contact类

4

代码如下,其中BsonId需要mongocsharpdriver。

1
2
3
4
5
6
7
8
9
public class Contact
    {
        [BsonId]
        public string Id { get; set; }
        public string Name { get; set; }
        public string Phone { get; set; }
        public string Email { get; set; }
        public DateTime LastModified { get; set; }
    }

我们需要添加mongosharpdriver。

7

8

另外我们需要在Model中添加Repository,Controller通过该类来访问Mongo DB。

1
2
3
4
5
6
7
public interface IContactRepository {
        IEnumerable GetAllContacts();
        Contact GetContact(string id);
        Contact AddContact(Contact item);
        bool RemoveContact(string id);
        bool UpdateContact(string id, Contact item);  
    }

ContactRepository的完整实现如下,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
public class ContactRepository : IContactRepository
    {
        MongoServer _server = null;
        MongoDatabase _database = null;
        MongoCollection _contacts = null;
        public ContactRepository(string connection)
        {
            if (string.IsNullOrWhiteSpace(connection))
            {
                connection = "mongodb://localhost:27017";
            }
            _server = new MongoClient(connection).GetServer();
            _database = _server.GetDatabase("Contacts");
            _contacts = _database.GetCollection("contacts");
            // Reset database and add some default entries
            _contacts.RemoveAll();
            for (int index = 1; index < 5; index++)
            {
                Contact contact1 = new Contact
                {
                    Email = string.Format("test{0}@example.com", index),
                    Name = string.Format("test{0}", index),
                    Phone = string.Format("{0}{0}{0} {0}{0}{0} {0}{0}{0}{0}", index)
                };
                AddContact(contact1);
            }
        }
        public IEnumerable GetAllContacts()
        {
            return _contacts.FindAll();
        }
        public Contact GetContact(string id)
        {
            IMongoQuery query = Query.EQ("_id", id);
            return _contacts.Find(query).FirstOrDefault();
        }
        public Contact AddContact(Contact item)
        {
            item.Id = ObjectId.GenerateNewId().ToString();
            item.LastModified = DateTime.UtcNow;
            _contacts.Insert(item);
            return item;
        }
        public bool RemoveContact(string id)
        {
            IMongoQuery query = Query.EQ("_id", id);
            WriteConcernResult result = _contacts.Remove(query);
            return result.DocumentsAffected == 1;
        }
        public bool UpdateContact(string id, Contact item)
        {
            IMongoQuery query = Query.EQ("_id", id);
            item.LastModified = DateTime.UtcNow;
            IMongoUpdate update = Update
                .Set("Email", item.Email)
                .Set("LastModified", DateTime.UtcNow)
                .Set("Name", item.Name)
                .Set("Phone", item.Phone);
            WriteConcernResult result = _contacts.Update(query, update);
            return result.UpdatedExisting;
        }
    }

3. 添加Controller

右键Controllers目录选择添加Controller

5

选择Empty API controller,将Controller命名为ContactsController

6

添加如下代码,可以看到Controller中的API方法名就是以http verb命名的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class ContactsController : ApiController
    {
        private static readonly IContactRepository _contacts = new ContactRepository(string.Empty);
        public IQueryable Get()
        {
            return _contacts.GetAllContacts().AsQueryable();
        }
        public Contact Get(string id)
        {
            Contact contact = _contacts.GetContact(id);
            if (contact == null)
            {
                throw new HttpResponseException(HttpStatusCode.NotFound);
            }
            return contact;
        }
        public Contact Post(Contact value)
        {
            Contact contact = _contacts.AddContact(value);
            return contact;
        }
        public void Put(string id, Contact value)
        {
            if (!_contacts.UpdateContact(id, value))
            {
                throw new HttpResponseException(HttpStatusCode.NotFound);
            }
        }
        public void Delete(string id)
        {
            if (!_contacts.RemoveContact(id))
            {
                throw new HttpResponseException(HttpStatusCode.NotFound);
            }
        }
    }

4. 添加View

首先添加Knockoutjs库,

9

Knockoutjs通过MVVM模式来实现动态html绑定数据,如下图,其中View-Model是客户端的JavaScript object保存的model数据。

webapi_ef16

先打开HomeController,里面添加一个新的Action代码如下,因为我们要在MVC中对于ContactsController添加对应的View。

1
2
3
4
5
6
7
public ActionResult Admin()
        {
            string apiUri = Url.HttpRouteUrl("DefaultApi", new { controller = "contacts", });
            ViewBag.ApiUrl = new Uri(Request.Url, apiUri).AbsoluteUri.ToString();
            return View();
        }

然后右键Admin方法,选择添加View

10

选择Create strongly-typed view,在model class中选择Contact类。

11

添加View的完整代码,注意view中我们通过js去访问WebAPI,以及通过动态绑定将数据呈现在网页上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
@model WebAPIDemo.Models.Contact
@{
    ViewBag.Title = "Admin";
}
@section Scripts {
  @Scripts.Render("~/bundles/JQueryval")
  <script type="text/JavaScript" src="@Url.Content("~/Scripts/knockout-2.3.0.js")"></script>
  <script type="text/javascript">
      function ProductsViewModel() {
          var self = this;
          self.products = ko.observableArray();
          var baseUri = '@ViewBag.ApiUrl';
          self.create = function (formElement) {
              // If valid, post the serialized form data to the web api
              $(formElement).validate();
              if ($(formElement).valid()) {
                  $.post(baseUri, $(formElement).serialize(), null, "json")
                      .done(function (o) { self.products.push(o); });
              }
          }
          self.update = function (product) {
              $.ajax({ type: "PUT", url: baseUri + '/' + product.Id, data: product });
          }
          self.remove = function (product) {
              // First remove from the server, then from the UI
              $.ajax({ type: "DELETE", url: baseUri + '/' + product.Id })
                  .done(function () { self.products.remove(product); });
          }
          $.getJSON(baseUri, self.products);
      }
      $(document).ready(function () {
          ko.applyBindings(new ProductsViewModel());
      })
  </script>
}
<h2>Admin</h2>
<div class="content">
    <div class="float-left">
    <ul id="update-products" data-bind="foreach: products">
        <li>
            <div>
                <div class="item">ID</div> <span data-bind="text: $data.Id"></span>
            </div>
            <div>
                <div class="item">Name</div>
                <input type="text" data-bind="value: $data.Name"/>
            </div>
            <div>
                <div class="item">Phone</div>
                <input type="text" data-bind="value: $data.Phone"/>
            </div>
            <div>
                <div class="item">Email</div>
                <input type="text" data-bind="value: $data.Email"/>
            </div>
            <div>
                <div class="item">Last Modified</div> <span data-bind="text: $data.LastModified"></span>
            </div>
            <div>
                <input type="button" value="Update" data-bind="click: $root.update"/>
                <input type="button" value="Delete Item" data-bind="click: $root.remove"/>
            </div>
        </li>
    </ul>
    </div>
    <div class="float-right">
    <h2>Add New Product</h2>
    <form id="addProduct" data-bind="submit: create">
        @Html.ValidationSummary(true)
        <fieldset>
            <legend>Contact</legend>
            @Html.EditorForModel()
            <p>
                <input type="submit" value="Save" />
            </p>
        </fieldset>
    </form>
    </div>
</div>

接下来在_layout.cshtml中添加一个admin页面的链接如下

1
2
3
4
5
6
<ul id="menu">
    <li>@Html.ActionLink("Home", "Index", "Home", new { area = "" }, null)</li>
    <li>@Html.ActionLink("API", "Index", "Help", new { area = "" }, null)</li>
    <li>@Html.ActionLink("Admin", "Admin", "Home")</li>
</ul>

5. 测试与调试

大功告成,直接运行下我们的作品,我们的admin链接也显示在右上角,

12

Admin页面的样子,Contact list是动态加载进来的,可以通过这个页面做添加,修改,删除的操作。

13

通过IE network capture来查看请求内容,

重新加载页面,可以看到回复的格式为JSON,

14

JSON内容就是我们mock的一些数据。

image

接下来我们修改,删除,又添加了一条记录,可以看到使用了不同的http method。

image

通过前面安装的mongovue来查看下DB种的数据,先添加的user也在其中,令我感到欣慰。。。

image

其实还有两个有趣的实例,不过文章一写就长了,不好意思耽误大家时间,只好先放放,以后再写。

阿里云ECS(Ubuntu)单节点Kubernetes部署——小白教程 - LifeOfCoding - 博客园

mikel阅读(850)

来源: 阿里云ECS(Ubuntu)单节点Kubernetes部署——小白教程 – LifeOfCoding – 博客园

目录

参考资料

前言

准备

环境、工具

kubeadm、kubectl、kubelet简介

安装kubeadm、kubectl、kubelet

部署前准备

开始部署
查看部署状态
安装网络插件
测试
排错工具
友情链接

参考资料:


前言

这篇文章是比较久之前写的了,无聊翻了下博客发现好几篇博文排版莫名其妙的变了… 于是修改并完善了下。当初刚玩k8s的时候真的是踩坑踩到怕,官方的指引很多不能直接使用,然后百度上找到的很多资料都是直接搬运官网ε=(´ο`*)))唉。 最后还是硬着头皮百度谷歌了很久才算入了门。在这里写下这篇文章帮助下其他孩纸。

准备

在撸起袖子开干之前最好是对k8s有点基本的认识,比如什么是kubelet,啥是kubectl,配置文件又是什么东东之类的基础。可以翻看我其它有关k8s的文章简单了解下,虽然写得不好,但也不会很辣眼睛。有能力的还是到谷歌和官网看看(看英文版)。

环境、工具

阿里云学生机ECS、Ubuntu、docker、kubectl1.15.4、kubelet1.15.4、kubeadm1.15.4、


kubeadm、kubectl、kubelet简介

kubeadm

用于部署k8s的工具很多,kubeadm就是其中之一,因为比较简单、好上手所以用的比较多,这是一个命令行的工具,可以通过一条简单的命令就把k8s部署好。

kubectl

kubectl也是一个命令行工具,用来管理集群。在用kubeadm工具部署好集群环境后,几乎所有的操作都是通过kubectl来完成的。啥操作?那多了去咯,譬如在k8s中运行一个应用、部署应用服务、查看集群环境等等。最基本的一条命令就是

kubectl create

这条命令用于创建资源,什么是资源?k8s集群中所有东西都可以看做是资源,如用户凭证,用户角色以及用于 ”存放“ 容器的pod等。

kubectl create -f xxx.yaml

如上命令就是用配置文件创建资源的例子。”-f“ 用来指定配置文件,”xxx.yaml“就是配置文件,配置文件可以是yaml格式也可以是json,不过yaml用的比较多。配置文件基本上就是对资源属性的描述,并不难。
配置文件可以是本地文件也可以是网络上的文件,k8s官网的很多配置文件都是直接引用”raw.githubusercontent.com“的内容。当然,不用配置文件,直接使用命令行也是可以创建一些简单资源的:

kubectl create serviceaccount admin-user -n kubernetes-dashboard

这就创建了一个serviceaccount资源。

kubelet

kubelet是一个自动管理、调度集群资源的程序,不需要像kuectl一样手动去操纵它从而完成某些需求,只要懂得如何进行配置,如何查看日志即可。kubelet的配置项非常非常多,但是作为新手不用太过于关注,简单了解即可。

安装kubeadm、kubectl、kubelet

配置软件源

默认apt软件源里没有这几个软件,需要添加谷歌官方的软件源。但是因为官方提供的源无法访问,所以这里改成阿里的源:

curl -s https://mirrors.aliyun.com/kubernetes/apt/doc/apt-key.gpg | apt-key add -
cat <<EOF >/etc/apt/sources.list.d/kubernetes.list
deb https://mirrors.aliyun.com/kubernetes/apt/ kubernetes-xenial main
EOF
apt-get update

命令说明:
1.通过下载工具下载位于https://mirrors.aliyun.com/kubernetes/apt/doc/apt-key.gpg的deb软件包密钥,然后通过”apt-key”命令添加密钥
2.通过cat把源deb https://mirrors.aliyun.com/kubernetes/apt/ kubernetes-xenial main写入到”/etc/apt/sources.list.d/kubernetes.list”

*此处下载工具使用curl,若未安装,先执行如下命令安装。“apt-transport-https”工具允许apt通过https来下载软件,可以不用安装这个,只装curl

apt-get update && apt-get install -y apt-transport-https curl

完成以上步骤,可通过”apt-key list”命令看到类似如下的密钥信息:

查看”/etc/apt/sources.list.d/kubernetes.list”,如下:

选择软件版本

kubeadm、kubectl、kubelet三者的版本要一致,否则可能会部署失败,小版本号不同倒也不会出什么问题,不过尽量安装一致的版本。记住kubelet的版本不可以超过API server的版本。例如1.8.0的API server可以适配 1.7.0的kubelet,反之就不行了。
可以通过”apt-cache madison”命令来查看可供安装的软件的版本号
例:

apt-cache madison kubeadm kubelet kubectl

开始安装

这里安装的版本是”1.15.4-00″,别忘了后面的”-00″。
需要注意,安装kubeadm的时候,会自动安装kubectl、kubelet和cri-tool,安装kubelet时则会自动安装kubernetes-cni,如下:

然而这并不是一件好事,仔细看会发现自动安装的kubectl和kubelet是最新版本的,与kubeadm版本不一致。。。
所以应该更改安装顺序,先安装kubectl和kubelet,命令如下:

apt-get install kubectl=1.15.4-00 kubelet=1.15.4-00 kubeadm=1.15.4-00

如果不想让软件更新,可以输入:

apt-mark hold kubeadm kubectl kubelet

允许更新:

apt-mark unhold kubeadm kubectl kubelet

部署前准备

关闭防火墙

阿里云ECS的ufw默认是”inactive”,即处于关闭状态,这步可以跳过
在ubuntu下,可以使用”ufw”管理防火墙。
查看防火墙状态:

ufw status 

禁用防火墙:

ufw diable

启用防火墙:

ufw enable

关闭selinux(可跳过)

阿里云ecs没有selinux,这步跳过。如果是其它服务器提供商,如下是网上找到的方法,在此不作验证:

  • 修改/etc/selinux/config文件中设置SELINUX=disabled,然后重启服务器
  • 使用setenforce
    • setenforce 1 设为enforcing模式
    • setenforce 0 设为permissive模式

关闭swap

kubelet1.8+要求把swap关闭,详细原因可以访问Kubernetes 设计分析: 为什么 kubelet 运行时不能打开 swap?
如果你非要开启swap的话,可以配置”–fail-swap-on flag”参数。

  • 临时修改,重启复原
    • 关闭
    swapoff -a
    
    • 开启
    swapon -a
    
  • 永久修改,重启生效
    1. 把根目录文件系统设为可读写
    sudo mount -n -o remount,rw /
    
    1. 修改”/etc/fstab”文件,在”/swapfile”一行前加#禁用并保存退出重启服务器

配置并开启kubelet服务(可跳过)

貌似安装kubelet的时候会自动配置好。这步可以跳过。

kubelet --network-plugin=cni
systemctl enable kubelet

修改Docker的cgroup-driver

docker的cgroup-driver应该与kubelet的一致,kubelet默认为“systemd”。

方法一,修改docker的cgroup-driver为systemd

编辑”/etc/docker/daemon.json”(如果没有这个文件就自己新建一个)
添加如下信息:

"exec-opts": ["native.cgroupdriver=systemd"]

注意,如果之前添加了其它的镜像源,或者添加了其它参数,需要在其它配置项后用“,”逗号隔开,再添加新的配置项,否则配置文件会解析失败。
修改完后保存,重新载入配置文件,重启docker

systemctl daemon-reload
systemctl restart docker

方法二,修改kubelet的cgroup-driver为cgroupfs

修改”/etc/systemd/system/kubelet.service.d/10-kubeadm.conf”文件(该文件在安装kubelet时生成),在”Environment=”KUBELET_KUBECONFIG_ARGS=”后增加–cgroup-driver=cgroupfs

Environment="KUBELET_KUBECONFIG_ARGS=--bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf --cgroup-driver=cgroupfs"

配置iptable转发规则

docker 1.13+ 版本,启动后默认把iptable的FORWARD策略改为DROP,可能会影响k8s集群报文转发功能,需要修改FORWARD链的默认策略为ACCEPT。

  • 查看iptables策略
iptables -vnL
  • 临时修改
iptables -P FORWARD ACCEPT
  • 永久修改
    修改docker的service文件,文件路径”/lib/systemd/system/docker.service”,添加
ExecStartPost=/sbin/iptables -P FORWARD ACCEPT  # 当docker启动后会额外执行此条命令。此行加到ExecStart行下面。

重启docker

systemctl daemon-reload
systemctl start docker
systemctl enable docker

开始部署

kubeadm init --kubernetes-version=v1.15.4 --ignore-preflight-errors=NumCPU --pod-network-cidr=10.244.0.0/16 --image-repository registry.aliyuncs.com/google_containers

参数说明:

  • “kubernetes-version”:指定k8s的版本,对于不同版本k8s,kubeadm会去拉取不同版本的镜像。
  • “–pod-network-cidr”: 指定使用”cidr”方式给pod分配IP,参数的数值必须跟接下来要安装的网络插件flannel的配置一致, 否则flannel部署失败。后面用到的网络插件为flannel,flannel配置文件中默认的数值是“10.244.0.0/16”。
  • “–image-repository”: 指定镜像仓库,kubeadm默认的仓库地址没法访问,需要指定为阿里云的地址——”registry.aliyuncs.com/google_containers”
  • “–ignore-preflight-errors”:kubeadm在初始化之前,会执行“preflight”(预检),当条件不满足时会报错并停止初始化进程,但是有些报错并不会影响集群的初始化,因此可以指定该参数忽略特定的错误。阿里云学生机cpu只有单核,而k8s要求双核及以上,所以需要指定参数”–ignore-preflight-errors=NumCPU”。
    该参数的用法如下:
--ignore-preflight-errors=<option>


<option>就是错误的类型,如上图所示,错误提示是”[ERROR NumCPU]”,那么参数就写成:

--ignore-preflight-errors=NumCPU

查看部署状态


当看到上述信息就表示集群Master节点初始化成功,在同一网络下的机器上同样地安装kuneadm、kubelet并配置好环境之后,即可通过”kubeadm join”命令连接到Master节点使集群成为多节点集群:

kubeadm join 192.168.1.73:6443 --token gkp9ws.rv2guafeusg7k746 \
    --discovery-token-ca-cert-hash sha256:4578b17cd7198a66438b3d49bfb878093073df23cf6c5c7ac56b3e05d2e7aec0

该token默认有效期为24小时,可通过”kubeadm token create –print-join-command”命令创建新token,并打印连接命令:


初始化成功后,在master节点上可以通过kubectl命令来查看集群上资源的状态,但是kubectl在访问API Server时需要认证,否则会出现”The connection to the server localhost:8080 was refused”这样的错误。有如下两种解决方法:
一、
认证信息在”/etc/kubernetes/admin.conf”文件里,所以需要把文件拷贝到”$HOME/.kube/config/”下,

mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

这段命令会在初始化成功后打印出来,直接拷贝即可。

二、
也可以直接添加环境变量指定文件地址:

export KUBECONFIG=/etc/kubernetes/admin.conf

可以把这条变量添加到”/etc/profile”,然后”source /etc/profile”。

配置好后,通过”kubectl get node”即可看到集群的所有节点状态:

可以看到当前节点处于”NotReady”状态,通过”kubectl describe node <your_node>”命令查看node情况,<your_node>替换成自己的节点名,此时可以看到这样的信息:

在”Ready”一行,”status”为”false”,”message”提示”Runtime newtwork not ready”,意思是网络插件未准备好,所以此时应该给集群安装网络插件。


安装网络插件

网络插件有很多种,此处选择”flannel”,flannel的安装比较简单,直接指定配置文件,用”kubectl”安装即可。配置文件如下:

flannel.yaml

博客园markdown的details标签不好用,格式可能有点问题,给个github的传送门


128行的network数值应与”kubeadm”初始化时指定的”–pod-network-cidr”一致,该值表示给pod分配ip时的ip前缀。

创建好配置文件后,通过”kubeclt”安装flannel:

kubectl create -f <flannel_yaml_path>

把<flannel_yaml_path>替换成自己的配置文件路径,执行命令后稍等片刻,单节点k8s集群就部署好了。


测试

处理taint

默认情况下,出于安全考虑,master节点会被打上一个叫”NoSchedule”的Taint(污点),可以通过”kubectl describe”看到:

这个taint使得master节点不能被调度,也就是说master节点不能部署应用,由于现在搭建的是单节点集群,当前节点既充当master又得充当worker,所以需要把这个taint去掉:

kubectl taint node <node_name> <taint>-

<node_name>替换成自己节点名称,<taint>替换成taint,如:

kubectl taint node yo node-role.kubernetes.io/master:NoSchedule-

注意别忘了taint后面的横杠”-“,”-“表示“减号”,即把taint去掉。如果想把”taint”加回去,只需要把”-“去掉,并在”:”前加上”=”,如”kubectl taint node yo node-role.kubernetes.io/master=:NoSchedule”

运行nginx:

docker pull nginx
kubectl run nginx --image=nginx

稍等片刻nginx就部署好了,可以通过”kubectl get pods –all-namespaces”查看,或者直接访问”curl localhost:80″。


排错工具

kubectl

kubectl get <resource_type>

“kubectl get”可以列出集群环境中的某类资源,对于k8s,几乎所有内容都是“资源”,如Node、Pod、Service等,只要把”<resource_type>”替换成想查看的资源类型即可。
如查看节点资源:

kubectl get node


对于pod等其它的资源,kubectl的用法会有些许不同。k8s使用”namespace”来对集群中的资源进行分组管理,可以把”namespace”当做“分组的名称”,也可以把”namespace”当做k8s集群中的”集群”,不同”namespace”的资源在逻辑上彼此隔离,以此提高安全性,提高管理效率。用kubeectl查看这些个资源时,需要用”-n”来指定”namespace”,如:

kubectl get pod -n kube-system


还可以用”–all-namespaces”来查看所有”namespaces”下的资源:

kubectl get pod --all-namespaces


kubectl describe <resource_type> <resource_name>

对于处于异常状态的资源,可以使用该命令查看其详细信息,只要把<resource_type>替换成资源类别,把<resource_name>替换成资源名称即可,当然也还需要用”-n”指明”namespaces”。
如:

kubectl describe pod -n kubernetes-dashboard kubernetes-dashboard-6b855d4584-9sgsk

然后就可以看到该pod的事件信息:


docker ps -a

该命令可以查看当前所有docker容器,”-a”表示所有容器,当不加该参数时,显示的则是正在运行的容器。由于要查看的是k8s相关的容器,以此可以使用管道命令和”grep”对显示结果进行筛选:

docker ps -a | grep kube

对于处于”Exited”状态的异常容器,使用”docker logs <container_id>”命令查看容器日志。如:

docker logs 37443d902aee

此处”37443d902aee”是我机器上”kubernetes-dashboard”的容器id。


友情链接

一小时快速搭建基于阿里云容器服务-Kubernetes的Web应用 - zhaowei121 - 博客园

mikel阅读(561)

来源: 一小时快速搭建基于阿里云容器服务-Kubernetes的Web应用 – zhaowei121 – 博客园

本文面向的读者

如果您是一个Kubernetes的初学者,本文可以帮助你快速在云上搭建一个可实际使用的集群环境,并发布自己的第一个应用。你无须提前准备任何的硬件资源或者下载任何的软件包。
如果您已经有一个自建的Kubernetes集群,想要尝试阿里云上的托管集群,本文可以帮助你快速完成上手操作,而无需详细阅读阿里云的帮助文档,从而节省您的时间。您可以在有了端到端的初体验之后,再有选择的阅读容器服务和容器镜像服务的帮助文档。
如果你已经有一个传统的部署在云上的Web应用(比如部署在云上的ECS里),想要进行容器化的改造,本文同样可以帮助到您,您甚至无需深入学习Kubernetes,只需了解基本概念即可。

准备代码

本文的操作全部基于阿里云控制台,因此您只需要一个阿里云控制台的登录账号即可。

我们先把应用的代码准备好。请登录https://code.aliyun.com/ ,登录完成后,访问https://code.aliyun.com/shengbo.tsb/yunputest ,点击派生项目(fork)的图标。

在随后弹出的确认框里,点击头像确认,完成派生。

备选方案:如果您派生遇到了困难,可以直接从https://github.com/docker-training/webapp clone这个项目,然后自己通过git push到code.aliyun.com上。

准备镜像仓库

登录https://cr.console.aliyun.com/cn-beijing/instances/repositories
在左侧导航栏选择“命名空间”

点击“创建命令空间”,输入名字“tengshengbo”。

确认命名空间创建完成之后,点击左侧导航栏“镜像仓库”,点击“创建镜像仓库”,输入仓库名称“yunputest”

在下一步,选择刚刚建立的代码仓库。选择“海外机器构建”以加快构建速度。提示:如果下拉框没有代码仓库,点击右侧的刷新按钮。

创建新镜像

回到https://code.aliyun.com/ 点击yunputest这个项目,注意,是自己名下的项目,而不是shengbo.tsb这个账号名下的项目。 点击“新标签”.

输入标签名release-v1.5。注意,您必须使用release-v[版本号]形式,比如release-v2.5或者其他。

返回镜像仓库列表,https://cr.console.aliyun.com/cn-beijing/instances/repositories

选择自己的镜像仓库,左侧导航选择“构建”,可以看到一个构建自动生成了。

创建Kubernetes集群

访问容器服务控制台 https://cs.console.aliyun.com/#/k8s/cluster/list

选择“创建Kubernetes集群”, 集群模板选择第一个“标准托管集群”

集群名称选择“yunpu-k8s”,选择专有网络和虚拟交换机,勾选公网访问(为了开发测试方便),选择实例类型(如果不考虑性能,单纯为了省钱,可以使用t5或者t6实例),其他默认就好。

注意:Pod CIDR,Service CIDR不能与VPC内已有网段冲突,如果提示有错误,请更换网络的CIDR。

在确认对话框里勾选协议,完成创建。之后,确认自己的集群运行中。

部署第一个应用

容器服务的左侧导航栏选择“应用-无状态”,点击“使用镜像创建”,在“应用基本信息”这一部分, 输入应用名称yunpuapp, 选择刚创建的集群yunpu-k8s, 其他默认。

下一步,容器配置,镜像名称一项,点击“选择镜像”,选择刚才自动从代码创建的镜像

镜像Tag一项,点击“选择镜像Tag”,选择你刚刚自己定义的版本号,比如1.4。其他保持默认。

点击下一步。

在高级配置里面,点击“服务(Service)”旁边的“创建”,

端口映射一项,输入名称port,注意服务端口80,容器端口5000.

点击“路由(Ingress)”旁边的“创建”,输入域名yunpuapp选择刚刚创建的服务以及port。

创建成功之后,可以看到应用的访问方式,在“路由(Ingress)”里面,“规则”一项,有一个链接。

点击这个链接,可以访问应用,显示内容包含响应时间、应用运行时的host name以及IP地址。多次刷新链接,可以看到不同的ip地址,这个说明后面对应的容器组Pod是多个。

升级应用

建议您自行尝试更改应用的代码,比如简单更改webapp/app.py这个文件,完成git tag后,git push.

或者,您也可以直接在 https://code.aliyun.com/ 控制台上,为master分支新建标签release-v1.5

等待镜像更新完成后,在“无状态应用”页,点击应用旁边的“编辑”。

编辑页面,选择新的版本1.5,点击更新

下一步

阿里云的容器服务Kubernetes不是只有托管版,还有专有版和Serverless版。专有版适合有丰富的容器运维经验,希望长时间运行某个应用,并且独立自主运维全部基础设施的的大企业用户。Serverless版适合不希望关心基础设施的运维,或者只是短时间运行某个应用从而希望开箱即用的中小企业用户。本文介绍的托管版则介于专有版和Serverless版两者之间。

如果您想了解更多,建议参考以下资料:
容器服务Kubernetes版帮助文档: https://help.aliyun.com/product/85222.html
容器镜像服务帮助文档: https://help.aliyun.com/product/60716.html

如果您需要针对容器服务进行运维,参考以下资料:
运维编排:
https://help.aliyun.com/product/119529.html
资源编排:
https://help.aliyun.com/product/28850.html

 

本文作者:云普

原文链接

本文为阿里云内容,未经允许不得转载。