使用Identity Server 4建立Authorization Server (6) - js(angular5) 客户端

预备知识: 学习Identity Server 4的预备知识

第一部分: 使用Identity Server 4建立Authorization Server (1)

第二部分: 使用Identity Server 4建立Authorization Server (2)

第三部分: 使用Identity Server 4建立Authorization Server (3)

第四部分: 使用Identity Server 4建立Authorization Server (4)

第五部分: 使用Identity Server 4建立Authorization Server (5)

由于手头目前用项目, 所以与前几篇文章不同, 这次要讲的js客户端这部分是通过我刚刚开发的真是项目的代码来讲解的.

这是后端的代码: https://github.com/solenovex/asp.net-core-2.0-web-api-boilerplate

这里面有几个dbcontext, 需要分别对Identity Server和Sales.DataContext进行update-database, 如果使用的是Package Manager Console的话.

进行update-database的时候, 如果是针对IdentityServer这个项目的要把IdentityServer设为启动项目, 如果是针对Sales.DataContext的, 那么要把SalesApi.Web设为启动项目, 然后再进行update-database.

项目结构如图:

目前项目只用到AuthorizationServer和Sales这两部分.

首先查看AuthorizationServer的相关配置: 打开Configuration/Config.cs

ApiResource:

public static IEnumerable<ApiResource> GetApiResources()
{        
    
return new List<ApiResource>{   new ApiResource(CoreApiSettings.ApiResource.Name, CoreApiSettings.ApiResource.DisplayName) { },                new ApiResource(SalesApiSettings.ApiResource.Name, SalesApiSettings.ApiResource.DisplayName) {UserClaims = { JwtClaimTypes.Name, JwtClaimTypes.PreferredUserName, JwtClaimTypes.Email }}};}


红色部分是相关代码, 是所需要的ApiResource的定义. 

其中需要注意的是, 像user的name, email等这些claims按理说应该可以通过id_token传递给js客户端, 也就是IdentityResource应该负责的. 但是我之所以这样做是因为想把这些信息包含在access_token里面, 以便js可以使用包含这些信息的access_token去访问web api, 这样 web api就可以直接获得到当前的用户名(name), email了. 标准的做法应该是web api通过访问authorization server的user profile节点来获得用户信息, 我这么做就是图简单而已.

所以我把这几个claims添加到了ApiResource里面. 

配置好整个项目之后你可以把 name 去掉试试, 如果去掉的话, 在web api的controller里面就无法取得到user的name了, 因为js收到的access token里面没有name这个claim, 所以js传给web api的token里面也没有name. 这个一定要自己修改下试试.

然后配置Client:

public static IEnumerable<Client> GetClients()
{       
     
return new List<Client>{                // Core JavaScript Clientnew Client{ClientId = CoreApiSettings.Client.ClientId,ClientName = CoreApiSettings.Client.ClientName,AllowedGrantTypes = GrantTypes.Implicit,AllowAccessTokensViaBrowser = true,RedirectUris =           { CoreApiSettings.Client.RedirectUri, CoreApiSettings.Client.SilentRedirectUri },PostLogoutRedirectUris = { CoreApiSettings.Client.PostLogoutRedirectUris },AllowedCorsOrigins =     { CoreApiSettings.Client.AllowedCorsOrigins },AllowedScopes ={IdentityServerConstants.StandardScopes.OpenId,IdentityServerConstants.StandardScopes.Profile,IdentityServerConstants.StandardScopes.Email,CoreApiSettings.ApiResource.Name}},                // Sales JavaScript Client new Client{ClientId = SalesApiSettings.Client.ClientId,ClientName = SalesApiSettings.Client.ClientName,AllowedGrantTypes = GrantTypes.Implicit,AllowAccessTokensViaBrowser = true,AccessTokenLifetime = 60 * 10,AllowOfflineAccess = true,RedirectUris =           { SalesApiSettings.Client.RedirectUri, SalesApiSettings.Client.SilentRedirectUri },PostLogoutRedirectUris = { SalesApiSettings.Client.PostLogoutRedirectUris },AllowedCorsOrigins =     { SalesApiSettings.Client.AllowedCorsOrigins },//AlwaysIncludeUserClaimsInIdToken = true,AllowedScopes ={IdentityServerConstants.StandardScopes.OpenId,IdentityServerConstants.StandardScopes.Profile,IdentityServerConstants.StandardScopes.Email,SalesApiSettings.ApiResource.Name,CoreApiSettings.ApiResource.Name}}};}

红色部分是相关的代码.

AccessTokenLifeTime是token的有效期, 单位是秒, 这里设置的是 10 分钟.

AlwaysIncludeUserClaimsInIdToken默认是false, 如果写true的话, 那么返回给客户端的id_token里面就会有user的name, email等等user相关的claims信息.

然后是IdentityResource:

public static IEnumerable<IdentityResource> GetIdentityResources(){         
   
return new List<IdentityResource>{                new IdentityResources.OpenId(),                new IdentityResources.Profile(),                new IdentityResources.Email()};}

这里需要这三个IdentityResource, 其中的openId scope(identity resource)是必须要加上的, 如果没有这个openid scope, 那么这个请求也许是一个合理的OAuth2.0请求, 但它肯定不会被当作OpenId Connect 请求.

如果你把profile这项去掉, 其他相关代码也去掉profile, 那么客户端新请求的id_token是无论如何也不会包括profile所包含的信息的(name等), 但是并不影响api resource里面包含相关的claim(access_token还是可以获得到user的name等的).

其他的Identity Scopes(Identity Resource)所代表的内容请看文档: http://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims:

profile: namefamily_namegiven_namemiddle_namenicknamepreferred_username,profilepicturewebsitegenderbirthdatezoneinfolocale, and updated_at.

email: email and email_verified Claims.

address: address Claim.

phone: phone_number and phone_number_verified Claims.

看一下Authorization Server的Startup.cs:

namespace AuthorizationServer
{    
   
public class Startup{      
       
public Startup(IConfiguration configuration){Configuration = configuration;}        
       
       
public IConfiguration Configuration { get; }        public void ConfigureServices(IServiceCollection services){        
       
var connectionString = Configuration.GetConnectionString("DefaultConnection");            var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;services.AddDbContext<ApplicationDbContext>(options =>options.UseSqlServer(connectionString));services.AddIdentity<ApplicationUser, IdentityRole>(options =>{                // Password settingsoptions.Password.RequireDigit = false;options.Password.RequiredLength = 4;options.Password.RequireNonAlphanumeric = false;options.Password.RequireUppercase = false;options.Password.RequireLowercase = false;options.Password.RequiredUniqueChars = 1;                // Lockout settingsoptions.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);options.Lockout.MaxFailedAccessAttempts = 5;options.Lockout.AllowedForNewUsers = true;                // Signin settingsoptions.SignIn.RequireConfirmedEmail = false;options.SignIn.RequireConfirmedPhoneNumber = false;                // User settingsoptions.User.RequireUniqueEmail = false;                }).AddEntityFrameworkStores<ApplicationDbContext>().AddDefaultTokenProviders();services.ConfigureApplicationCookie(options =>{options.Cookie.Name = "MLHAuthorizationServerCookie";options.Cookie.HttpOnly = true;options.ExpireTimeSpan = TimeSpan.FromMinutes(60);options.LoginPath = "/Account/Login";options.LogoutPath = "/Account/Logout";options.AccessDeniedPath = "/Account/AccessDenied";options.SlidingExpiration = true;options.ReturnUrlParameter = CookieAuthenticationDefaults.ReturnUrlParameter;});services.AddTransient<IEmailSender, EmailSender>();services.AddMvc();services.AddAutoMapper();            services.AddIdentityServer()#if DEBUG.AddDeveloperSigningCredential() #else.AddSigningCredential(new System.Security.Cryptography.X509Certificates.X509Certificate2(SharedSettings.Settings.AuthorizationServerSettings.Certificate.Path, SharedSettings.Settings.AuthorizationServerSettings.Certificate.Password)) #endif.AddInMemoryIdentityResources(Config.GetIdentityResources()).AddInMemoryApiResources(Config.GetApiResources()).AddInMemoryClients(Config.GetClients()).AddOperationalStore(options =>{options.ConfigureDbContext = builder =>builder.UseSqlServer(connectionString,sql => sql.MigrationsAssembly(migrationsAssembly));options.EnableTokenCleanup = true;options.TokenCleanupInterval = 30;}).AddAspNetIdentity<ApplicationUser>();services.AddAuthorization(options =>{options.AddPolicy(CoreApiAuthorizationPolicy.PolicyName, policy =>policy.RequireClaim(CoreApiAuthorizationPolicy.ClaimName, CoreApiAuthorizationPolicy.ClaimValue));});}    
public void Configure(IApplicationBuilder app, IHostingEnvironment env){app.InitializeDatabase();  
         
if (env.IsDevelopment()){app.UseDeveloperExceptionPage();app.UseBrowserLink();app.UseDatabaseErrorPage();}          
           
else{app.UseExceptionHandler("/Home/Error");}app.UseStaticFiles();app.UseIdentityServer();app.UseMvc(routes =>{routes.MapRoute(name: "default",template: "{controller=Home}/{action=Index}/{id?}");});}} }

这里我只将Operation数据保存到了数据库. 而Client和ApiResource, IdentityResource等定义还是放在了内存中, 我感觉这样比较适合我.

Sales Web Api:

打开SalesApi.Web的Startup ConfigureServices: 这个非常简单:

            services.AddAuthentication("Bearer").AddIdentityServerAuthentication(options =>{options.Authority = AuthorizationServerSettings.AuthorizationServerBase;options.RequireHttpsMetadata = false;options.ApiName = SalesApiSettings.ApiResource.Name;});

没什么可说的.

js 客户端 和 oidc-client.js

无论你使用什么样的前端框架, 最后都使用oidc-client.js来和identity server 4来配套操作. 

我使用的是 angular 5: 由于这个代码是公司的项目, 后端处于早期阶段, 被我开源了, 没什么问题.

但是前端是某机构买的一套收费的皮肤, 所以没法开源, 这里我尝试提供部分代码, 我相信您一定可以从头搭建出完整的js客户端的.

我的前端应用流程是:

访问前端地址, 如果没有登录用户, 那么跳转到Authorization Server进行登陆, 同意后, 返回到前端的网站. 

如果前端网站有登录的用户, 那么在用户快过期的时候自动刷新token. 以免登陆过期.

前端应用访问api时, 自动拦截所有请求, 把登陆用户的access token添加到请求的authorization header, 然后再发送给 web api.

我把前端精简了一下, 放到了网盘,是好用的

链接: https://pan.baidu.com/s/1minARgc 密码: ipyw

首先需要安装angular-cli:

npm install -g @angular/cli

然后在项目根目录执行:

npm install

虽然npm有点慢, 但是也不要使用cnpm, 有bug.

js客户端参考

你可以参考官方文档: http://docs.identityserver.io/en/release/quickstarts/7_javascript_client.html

安装oidc-client:

地址是: https://github.com/IdentityModel/oidc-client-js,  查看文档的话点wiki即可.

在你的框架里面执行:

npm install oidc-client --save

配置oidc-client:

我的配置放在了angular5项目的environments里面, 因为这个配置根据环境的不同(开发和生产)里面的设定是不同的:

import { WebStorageStateStore } from 'oidc-client';
// The file contents for the current environment will overwrite these during build.
// The build system defaults to the dev environment which uses `environment.ts`, but if you do
// `ng build --env=prod` then `environment.prod.ts` will be used instead.
// The list of which env maps to which file can be found in `angular-cli.json`.export const environment = {production: false,authConfig: {authority: 'http://localhost:5000',client_id: 'sales',redirect_uri: 'http://localhost:4200/login-callback',response_type: 'id_token token',scope: 'openid profile salesapi email',post_logout_redirect_uri: 'http://localhost:4200',silent_redirect_uri: 'http://localhost:4200/silent-renew.html',automaticSilentRenew: true,accessTokenExpiringNotificationTime: 4,        // silentRequestTimeout:10000,userStore: new WebStorageStateStore({ store: window.localStorage })},salesApiBase: 'http://localhost:5100/api/sales/',themeKey: 'MLHSalesApiClientThemeKeyForDevelopment'};

authority就是authorization server的地址.

redirect_url是登陆成功后跳转回来的地址.

silent_redirect_uri是自动刷新token的回掉地址.

automaticSilentRenew为true是启用自动安静刷新token.

userStore默认是放在sessionStorage里面的, 我需要使用localStorage, 所以改了.

建立AuthService:

import { Injectable, EventEmitter } from '@angular/core';
import { Router } from '@angular/router';
import { Observable } from 'rxjs/Observable';
import { User, UserManager, Log } from 'oidc-client';
import 'rxjs/add/observable/fromPromise';
import { environment } from '../../../environments/environment';Log.logger = console;
Log.level = Log.DEBUG;@Injectable()
export class AuthService {private manager: UserManager = new UserManager(environment.authConfig);public loginStatusChanged: EventEmitter<User> = new EventEmitter();private userKey = `oidc.user:${environment.authConfig.authority}:${environment.authConfig.client_id}`;constructor(private router: Router) {        this.manager.events.addAccessTokenExpired(() => {this.login();});}login() {        this.manager.signinRedirect();}loginCallBack() {        return Observable.create(observer => {Observable.fromPromise(this.manager.signinRedirectCallback()).subscribe((user: User) => {                    this.loginStatusChanged.emit(user);observer.next(user);observer.complete();});});}tryGetUser() {        return Observable.fromPromise(this.manager.getUser());}logout() {        this.manager.signoutRedirect();}get type(): string {     
   
return 'Bearer';}get token(): string | null {const temp = localStorage.getItem(this.userKey);    
   
if (temp) {const user: User = JSON.parse(temp);        
   
return user.access_token;}        return null;}get authorizationHeader(): string | null {      
  
if (this.token) {            return `${this.type} ${this.token}`;}        return null;} }

UserManager就是oidc-client里面的东西. 我们主要是用它来操作.

constructor里面那个事件是表示, 如果用户登录已经失效了或者没登录, 那么自动调用login()登陆方法.

login()方法里面的signInRedirect()会直接跳转到Authorization Server的登陆窗口.

logout()里的signoutRedirect()就会跳转到AuthorizationServer并执行登出.

其中的userKey字符串是oidc-client在localStorage默认存放用户信息的key, 这个可以通过oidc-client的配置来更改.

我没有改, 所以key是这样的: "oidc.user:http://localhost:5000:sales":

Token Interceptor 请求拦截器:

针对angular 5 所有的请求, 都应该加上authorization header, 其内容就是 access token, 所以token.interceptor.ts就是做这个工作的:

import { Injectable } from '@angular/core';
import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import { User } from 'oidc-client';
import { environment } from '../../../environments/environment';
import { AuthService } from './auth.service';@Injectable()
export class TokenInterceptor implements HttpInterceptor {constructor(private authService: AuthService) { }intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {        const authHeader = this.authService.authorizationHeader;const authReq = req.clone({ headers: req.headers.set('Authorization', authHeader) });        return next.handle(authReq);}
}

angular 5 的interceptor不会修改request, 所以只能clone.

设置AuthGuard:

angular5的authguard就是里面有个方法, 如果返回true就可以访问这个路由, 否则就不可以访问.

所以我在几乎最外层添加了这个authguard, 里面的代码是:

import { Injectable } from '@angular/core';
import { CanActivate } from '@angular/router';
import { Router } from '@angular/router';
import { User } from 'oidc-client';
import { AuthService } from './../services/auth.service';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';@Injectable()
export class AuthGuard implements CanActivate {constructor(private router: Router,private authService: AuthService) { }canActivate(): Observable<boolean> {        return this.authService.tryGetUser().map((user: User) => {if (user) {return true;}this.authService.login();return false;});}
}

意思就是, 取当前用户, 如果有用户那么就可以继续访问路由, 否走执行登陆动作.

所以访问访问网站后会跳转到这, 这里有个内置用户 admin 密码也是admin, 可以使用它登陆.

外层路由代码app-routing.module.ts:

import { NgModule } from '@angular/core';
import { Routes } from '@angular/router';import { AuthGuard } from './shared/guards/auth.guard';import { MainComponent } from './main/main.component';
import { LoginCallbackComponent } from './shared/components/login-callback/login-callback.component';
import { NotFoundComponent } from './shared/components/not-found/not-found.component';export const AppRoutes: Routes = [{path: '',redirectTo: 'dashboard',pathMatch: 'full',
}, {path: 'login-callback',component: LoginCallbackComponent
}, {path: '',component: MainComponent,canActivate: [AuthGuard],children: [{path: 'dashboard',loadChildren: './dashboard/dashboard.module#DashboardModule'}, {path: 'settings',loadChildren: './settings/settings.module#SettingsModule'}]
},
{ path: '**', component: NotFoundComponent }];


登陆成功后首先会跳转到设置好的redirect_uri, 这里就是login-callback这个路由地址对应的component:


import { Component, OnInit } from '@angular/core';
import { AuthService } from '../../../shared/services/auth.service';
import { User } from 'oidc-client';
import { ToastrService } from 'ngx-toastr';@Component({selector: 'app-login-callback',templateUrl: './login-callback.component.html',styleUrls: ['./login-callback.component.css']
})
export class LoginCallbackComponent implements OnInit {constructor(private authService: AuthService,private toastr: ToastrService) { }ngOnInit() {        this.authService.loginCallBack().subscribe((user: User) => {this.toastr.info('登陆成功, 跳转中...', '登陆成功');if (user) {window.location.href = '/';}});}}


我在这里没做什么, 就是重新加载了一下页面, 我感觉这并不是好的做法.

您可以单独建立一个简单的页面就像官方文档那样, 然后再跳转到angular5项目里面.

这个页面一闪而过:

回到angular5项目后就可以正常访问api了.

自动刷新Token:

oidc-client的自动刷新token是只要配置好了, 你就不用再做什么操作了.

刷新的时候, 它好像是会在页面上弄一个iframe, 然后在iframe里面操作.

不过还是需要建立一个页面, 用于刷新:

<!DOCTYPE html><html><head><meta charset="utf-8" /><title></title></head><body><h1 id="waiting">Waiting...</h1><div id="error"></div><script src="assets/js/oidc-client.min.js"></script><script>new Oidc.UserManager().signinSilentCallback();    </script></body></html>


很简单就这些.

 

最后操作一下试试: 最好自己调试一下:

菜单那几个都是好用的页面.

相关文章: 

  • 基于OIDC(OpenID Connect)的SSO

  • 学习Identity Server 4的预备知识

  • 使用Identity Server 4建立Authorization Server (1)

  • 使用Identity Server 4建立Authorization Server (2)

  • 使用Identity Server 4建立Authorization Server (3)

  • 使用Identity Server 4建立Authorization Server (4)

  • 使用Identity Server 4建立Authorization Server (5)

  • IdentityServer4(10)- 添加对外部认证的支持之QQ登录


原文:http://www.cnblogs.com/cgzl/p/7894446.html


.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/322781.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SpringBoot整合kafka(实现producer和consumer)

转载自 SpringBoot整合kafka(实现producer和consumer) 在Windows环境下安装运行Kafka&#xff1a;https://www.jianshu.com/p/d64798e81f3b 本文代码使用的是Spring Boot 2.1.1.RELEASE 版本 <parent><groupId>org.springframework.boot</groupId><art…

ASP.NET Core 认证与授权[7]:动态授权

基于资源的授权 有些场景下&#xff0c;授权需要依赖于要访问的资源&#xff0c;例如&#xff1a;每个资源通常会有一个创建者属性&#xff0c;我们只允许该资源的创建者才可以对其进行编辑&#xff0c;删除等操作&#xff0c;这就无法通过[Authorize]特性来指定授权了。因为授…

H5的Websocket基本使用

前端代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title> </head> &…

springboot手动提交kafka offset

转载自 springboot手动提交kafka offset enable.auto.commit参数设置成了false 但是测试发现enable.auto.commit参数设置成了false&#xff0c;kafka的offset依然提交了&#xff08;也没有进行人工提交offset&#xff09;。 查看源码 如果我们enable.auto.commit设置为false…

可观测性与原生云监控

在近日发表的一篇文章中&#xff0c;Cindy Sridharan概括介绍了可观测性及其与原生云应用程序监控的关系。可观测性是一种理念&#xff0c;包括监控、日志聚合、指标和分布式跟踪&#xff0c;可以实时更深入地观察系统。 Sridharan的文章基于她就同一个主题所做的Velocity演讲。…

使用Microsoft.AspNetCore.TestHost进行完整的功能测试

简介 Microsoft.AspNetCore.TestHost是可以用于Asp.net Core 的功能测试工具。很多时候我们一个接口写好了&#xff0c;单元测试什么的也都ok了&#xff0c;需要完整调试一下&#xff0c;检查下单元测试未覆盖到的代码是否有bug。步骤为如下&#xff1a;程序打个断点->F5运行…

MockJs案例

有时候前端写好模板后&#xff0c;后端还完工&#xff0c;那么总不能一直让项目停滞吧&#xff0c;这里就用Mockjs来模拟后端接口的数据&#xff0c;让我们先人一步完成项目。 首先创建一个html&#xff0c;导入axios和mockjs 再用mock去拦截请求&#xff0c;如果后端接口写好了…

Entity Framework Core 使用HiLo生成主键

HiLo是在NHibernate中生成主键的一种方式&#xff0c;不过现在我们可以在Entity Framework Core中使用。所以在这篇内容中&#xff0c;我将向您在介绍如何在Entity Framework Core中使用HiLo生成主键。 什么是Hilo&#xff1f; HiLo是High Low的简写&#xff0c;翻译成中文叫高…

Echarts报错:Component series.lines not exists. Load it first.

前几天用的echarts标签是bootcdn的 <script src"https://cdn.bootcdn.net/ajax/libs/echarts/4.7.0/echarts-en.common.js"></script>用着官方给的案例还可以&#xff0c;但是一用gallery社区里面的例子就报错 后来经过不断调试终于知道是需要换个cdn&…

认识微软Visual Studio Tools for AI

微软已经发布了其 Visual Studio Tools for AI 的测试版本&#xff0c;这是微软 Visual Studio 2017 IDE 的扩展&#xff0c;可以让开发人员和数据科学家将深度学习模型嵌入到应用程序中。Visual Studio Tools for AI 工具同时支持 Microsoft 的 Cognitive Toolkit 和 Google 的…

mybatis源码阅读(一):SqlSession和SqlSessionFactory

转载自 mybatis源码阅读(一)&#xff1a;SqlSession和SqlSessionFactory 一、接口定义 听名字就知道这里使用了工厂方法模式&#xff0c;SqlSessionFactory负责创建SqlSession对象。其中开发人员最常用的就是DefaultSqlSession &#xff08;1&#xff09;SqlSession接口定义…

开源纯C#工控网关+组态软件(六)图元组件

一、 图元概述 图元是构成人机界面的基本单元。如一个个的电机、设备、数据显示、仪表盘&#xff0c;都是图元。构建人机界面的过程就是铺排、挪移、定位图元的过程。 图元设计是绘图和编码的结合。因为图元不仅有显示和动画&#xff0c;还有背后操纵动画的控制逻辑。 一个好…

git合并分支的策略(赞)

假设当前有两个分支 master和test&#xff0c;两个分支一模一样&#xff0c;都有这三个文件 现在test添加一个test4.txt&#xff0c;然后提交到本地&#xff08;git add . git commit&#xff09; 切换到master分支上&#xff0c;git checkout master git merge test 这样mase…

改造独立部署(SCD)模式下.NET Core应用程序 dotnet的exe文件启动过程

设置一个小目标 改造前 改造后 独立部署SCD模式&#xff0c;是指在使用dotnet publish 命令时带上-r 参数运行时标识符&#xff08;RID&#xff09;。 目标提出原因&#xff1a;SCD模式下文件太乱了&#xff0c;很多文件在开发时大多又涉及不到&#xff0c;发布后如果能把文件…

mybatis源码阅读(四):mapper(dao)实例化

转载自 mybatis源码阅读(四)&#xff1a;mapper(dao)实例化 在开始分析之前&#xff0c;先来了解一下这个模块中的核心组件之间的关系&#xff0c;如图&#xff1a; 1.MapperRegistry&MapperProxyFactory MapperRegistry是Mapper接口及其对应的代理对象工程的注册中心&…

自定义路由匹配和生成

前言 前两篇文章主要总结了CMS系统两个技术点在ASP.NET Core中的应用&#xff1a; 《ASP.NET Core 中的SEO优化&#xff08;1&#xff09;&#xff1a;中间件实现服务端静态化缓存》 《ASP.NET Core 中的SEO优化&#xff08;2&#xff09;&#xff1a;中间件中渲染Razor视图》…

如何封装并发布一个属于自己的ui组件库

以前就一直有个想法自己能不能封装一个类似于elementui一样的组件库&#xff0c;然后发布到npm上去&#xff0c;毕竟前端说白了&#xff0c;将组件v上去&#xff0c;然后进行数据交互。借助这次端午&#xff0c;终于有机会&#xff0c;尝试自己去封装发布组件库了 我这里了只做…

听云支持.NET Core的应用性能监控

随着微软于2017年8月正式发布.NET Core 2.0&#xff0c; .NET Core 社区开始活跃&#xff0c;众多.NET开发者开始向跨平台转变。 听云于2017年11月推出了.NET Core应用监控工具&#xff0c;和听云其他语言的监控工具一样&#xff0c;.NET Core应用监控工具具有以下特征&#xf…

mybatis源码阅读(五) ---执行器Executor

转载自 mybatis源码阅读(五) ---执行器Executor 1. Executor接口设计与类结构图 public interface Executor {ResultHandler NO_RESULT_HANDLER null;// 执行update&#xff0c;delete&#xff0c;insert三种类型的sql语句int update(MappedStatement ms, Object parameter…

[52ABP实战系列] .NET CORE实战入门第三章更新了

早安 各位道友好&#xff0c;.NET CORE入门视频的第三章也算录制完毕了。欢迎大家上传课网进行学习。 更新速度 大家也知道最近的社会新闻比较多。频繁发生404、关键字打不出来&#xff0c;我个人也在关注这些事件。导致精力分散&#xff0c;没有做到稳定更新&#xff0c;现在呢…