0%

web开发学习路线图

https://juejin.im/post/5ea39c93f265da47e84e8c8a

阮一峰博客

H5新增api

  • navigator.geolocation.getCurrentPosition(successCallback, errorCallback)
  • dragable=true
  • FileReader
  • Storage

js单线程、异步和任务队列

避免修改dom引起的竞争

JavaScript是单线程的,可是浏览器内部不是单线程的。一些I/O操作、dom操作、定时器的计时和事件监听由浏览器提供的其他线程来完成的

任务机制

对象放在heap(堆)里,常见的基础类型和函数放在stack(栈)里,函数执行的时候在栈里执行。其中会有异步操作,这些操作可能需要调用浏览器的其他线程去处理,操作结果由相应的回调函数响应,但是要先丢到任务队列中,等栈空了,则检查任务队列,将队列中的事件函数放到栈中执行。这个过程是循环不断地

为什么函数调用是用栈实现的

同源和跨域

参考Enabling CORS in ASP.NET Core

同源策略(Same Origin Policy)Web应用程序安全模型中的概念,具有相同的URL方案(HTTP或HTTPS),相同的主机名(域名)和相同的端口号(应用程序相互通信的端点),则认为它们具有相同的来源。同源策略要求请求的客户端(httpclient)应与服务器应用属于同一源点

如果两个URL具有相同的URL方案(HTTP或HTTPS),相同的主机名(域名)和相同的端口号(应用程序相互通信的端点),则认为它们具有相同的来源。禁止跨域访问,目的是为了隔离潜在的恶意脚本,以免破坏Web上的其他文档

如果服务器允许来自Origin(https://example.com)的跨域请求,则它将Access-Control-Allow-Origin标头设置为其值与请求中的源标头的值相匹配。如果服务器不包含此标头,则请求将失败。浏览器应接收响应数据,但是客户端不能访问该数据。

Startup.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
public void ConfigureServices(IServiceCollection services)
{
services.AddCors();
services.AddMvc();
...
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env){
app.UseCors(x => x
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader());
...
}

订阅 付费边界 访问管理策略的边界
影响成本的因素:资源类型 服务 位置
pricing carculator 产品定价计算器和总拥有成本计算器
降低成本:

  • 执行成本分析
  • 使用Azure顾问监视使用情况
  • 使用支出限制
  • 使用Azure预订 提前付款产品
  • 选择低成本地点和地区
  • 使用标签标识成本所有者
    cost manage工具
    改进应用程序SLA 承诺的性能目标 如某级别月累计停机时间不超过xxx

云计算概览

自称:经济高效、可缩放、具有弹性、始终保持最新状态、可靠且安全

  • 资本支出 (Capital Expenditure, CapEx)实体基础设施投入费用, 如服务器、存储、网络、备份、灾后重建、技术人员
  • 运营支出 (Operational Expenditure, OpEx) 服务/产品使用费用

issue: 测试从Azure Serveice 到 remote DB server的network connections
背景:老板要将正在开发的项目放到公网可以访问的服务器上以用来演示,迁移数据库到云端是费事且有额外成本的。

日前的实践找到了控制台的解决方法 创建App Service后其管理面板上Development Tools--SSH,其进入后是linux终端,路径在wwwroot下,常用网络连接工具见{% post-link LinuxTools Linux命令行工具 %} curl命令可用

benefit

The public cloud is a shared entity whereby multiple corporations each use a portion of the resources in the cloud. It is the benefit of using a public cloud service for the servers over an on-premises network

混合云(Hybrid Cloud)

公有云的缺点:使用公有云可能无法满足特定的安全要求;公有云可能无法满足政府政策、行业标准或法律要求;不拥有硬件或服务,也无法按照你的意愿管理它们;可能很难满足独特的业务需求,例如必须维护旧版应用程序

私有云服务和公有云服务的结合,即要架设开放的商用应用,又对部分资源存在硬件或自主管理方面的要求,宜选择私有云

例题:Suppose you have two types of applications: legacy applications that require specialized mainframe hardware and newer applications that can run on commodity hardware. Which cloud deployment model would be best for you?

A. Public cloud
B. Private cloud
C. Hybrid cloud

保留预置的(on-premise)服务器,并且扩展需求,宜选择混合云,即保留原服务器以消除迁移成本,且用公有平台(或其他资源)进行方便的扩展

例题:You have an on-premises network that contains 100 servers.
You need to recommend a solution that provides additional resources to your users. The solution must minimize capital and operational expenditure costs.

A. a complete migration to the public cloud
B. an additional data center
C. a private cloud
D. a hybrid cloud

极具争议的一道判断题:An organization that hosts its infrastructure in a private cloud can decommission its data center.

答案是False. 希望不要在考场上遇到这么坑爹的表述,题干问如果organization在私有云上架设自己的设施,那他自己数据中心是不可或缺的还是可以decommission(拆除),其想表达的是如果你用公有云或者混合云,就没有必要自己经营数据中心了。

IaaS PaaS SaaS

Infrastructure as a service (IaaS) is an instant computing infrastructure, provisioned and managed over the internet. It’s one of the four types of cloud services, along with software as a service (SaaS), platform as a service (PaaS), and serverless.
IaaS
IaaS 通常用于以下场景:迁移工作负载;测试和开发;存储、备份和恢复。
Caution! 虚拟机是IaaS

例题:Your company plans to migrate all its data and resources to Azure.
The company’s migration plan states that only platform as a service (PaaS) solutions must be used in Azure.
You need to deploy an Azure environment that supports the planned migration.
Solution: You create an Azure App Service and Azure SQL databases.

A: Correct. Azure SQL databases 也属于 PaaS,区别于在虚拟机中安装的SQL Server(IaaS)

例题:Your company plans to migrate all its data and resources to Azure.
The company’s migration plan states that only Platform as a Service (PaaS) solutions must be used in Azure.
You need to deploy an Azure environment that meets the company migration plan.
Solution: You create an Azure App Service and Azure Storage accounts.

A: False. Azure Storage accounts is IaaS

Cosmos DB is PaaS

区域、中心

Q:Deploying an app can be done directly to what level of physical granularity(尺度)? A:Region

You need to ensure that the services running on the virtual machines are available if a single data center fails.
Solution: You deploy the virtual machines to two or more regions.
or You deploy the virtual machines to two or more availability zones

区域间数据传输根据带宽收费

Azure availability zone can be used to protect access to Azure services from an Azure data center failure

计价

例题:If you create two Azure virtual machines that use the B2S size, each virtual machine will always generate the same monthly costs.(False)

Two virtual machines using the same size could have different disk configurations. Therefore, the monthly
costs could be different.

“pay-as-you-go”

When planning to migrate a public website to Azure, you must plan to pay monthly usage costs. .

“elasticity”

弹性计算, 系统监控工具控制,无需中断操作即可使分配的资源量与实际所需资源量相匹配。通过云灵活性,公司可避免就未用容量或闲置资源付费,且不必担心投入资金购买或维护额外的资源和设备。

订阅

An Azure AD tenant can have multiple subscriptions but an Azure subscription can only be associated with one Azure AD tenant.
将 Azure 订阅关联或添加到 Azure Active Directory 租户

资源组

A resource can interact with resources in other resource groups

Deleting the resource group will remove the resource group as well as all the resources in that resource group.

Resources from multiple different regions can be placed in a resource group.

  • 资源组中的所有资源应该具有相同的生命周期。 一起部署、更新和删除这些资源。
  • 每个资源只能存在于一个资源组中。可以将资源从一个资源组移到另一个组
  • 资源组中的资源可以位于与资源组不同的区域。

锁定资源以防止意外:
CanNotDelete和ReadOnly, 资源可以存在多个删除锁,自动继承上层锁。

Azure virtual machines should you use from the Azure portal to view service failure notifications that can
affect the availability of VM1(your virtual machine)?

例题:You need to view a list of planned maintenance events that can affect the availability of an Azure subscription.

On the Help and Support blade, there is a Service Health option. If you click Service Health, a new blade opens. The Service Health blade contains the Planned Maintenance link which opens a blade where you can view a list of planned maintenance events that can affect the availability of an Azure subscription.

Azure服务

An integrated solution for the deployment of code - Azure DevOps
A tool that provides guidance and recommendation to improve an Azure environment - zure Advisor
A simplified tool to build intelligent Artificial Intelligence (AI) applications - Azure Cognitive services
Monitors web applications - Azure Application Insights

规模集(scale set)

如用于创建并管理一组负载均衡的 VM,根据需求或定义的计划自动增减 VM 实例的数目,为应用程序提供搞可用性

DevTest Lab

开发测试实验室,利用基架/模板快速创建环境

存储账户

Data that is copied to Azure Storage account is maintained automatically in at least three copies.

存储账户的数据冗余选项有4个 每种都以不同的措施复制三次 见存储帐户概述

Cloud shell

Cloud shell就是页面上那个命令行工具,从中可以运行PowerShell命令或Bash命令

另install azure cli then it can be used in Command Prompt or Windows PowerShell

例题:An Azure administrator plans to run a PowerShell script that creates Azure resources. You need to recommend which computer configuration to use to run the script.
Solution: Run the script from a computer that runs Linux and has the Azure CLI tools installed.
Does this meet the goal?

answer:A PowerShell script is a file that contains PowerShell cmdlets and code. A PowerShell script needs to be run in PowerShell.
PowerShell can now be installed on Linux. However, the question states that the computer has Azure CLI tools, not PowerShell installed. Therefore, this solution does not meet the goal.

PowerShell已经是跨平台应用,可以装在linux,azure cli是command line interface可以认为是命令集合,依托于windows cmd或powershell。上面的题目想说powershell脚本运行基于powershell应用而不是azure cli package,私以为题目很无聊。
官方教程的练习:
What do you need to install on your machine to let you execute Azure CLI commands locally?
A.The Azure cloud shell B.The Azure CLI and Azure PowerShell C.Only the Azure CLI

  1. True or false: The Azure CLI can be installed on Linux, macOS, and Windows, and the CLI commands you use are the same in all platforms.
    A.True B.False
  2. Which parameter can you add to most CLI commands to get concise, formatted output?
    A.list B.table C.group

answer is C A B

Azure命令行没有ping,而使用tcpping

Data Lake

例题:You plan to store 20 TB of data in Azure. The data will be accessed infrequently and visualized by using Microsoft Power BI.
You need to recommend a storage solution for the data.
A. Azure Data Lake
B. Azure Cosmos DB
C. Azure SQL Data Warehouse
D. Azure SQL Database
E. Azure Database for PostgreSQL

answer is AC,Azure Data Lake

网络

Local Network Gateway 创建从azure到本地网关的连接

You have an Azure environment that contains 10 virtual networks and 100 virtual machines.
You need to limit the amount of inbound traffic to all the Azure virtual networks.
What should you create?
A. one application security group (ASG)
B. 10 virtual network gateways
C. 10 Azure ExpressRoute circuits
D. one Azure firewall

answer is D

Advanced

2020.9.26勉强通过 准备AZ-303 + AZ-304 即成为Azure Solutions Architect Expert

It certainly wouldn’t hurt you to have your Microsoft Azure Administrator in the bag before attempting to take down this colossal certification and its dual architecture-focused exams. ————《Which Azure certification is right for me?》

即建议通过AZ-104获得Azure Administrator Associate认证,难度只有两星,费用是115刀

知乎:Azure 框架设计师认证考试2020大更改

原AZ-103将被新的AZ-104替代,原AZ-300将被新的AZ-303替代,原AZ-301将被新的AZ-304替代
AZ-103的考试侧重点分配如下

管理 Azure 订阅和资源 (15-20%)
实施和管理存储 (15-20%)
部署和管理虚拟机 (VM) (15-20%)
配置和管理虚拟网络 (30-35%)
管理身份 (15-20%)

在新的AZ-104中,这些权重将会有一些修改。

订阅Subscription和身份认证Identity将会合并成单独的身份认证Identity
新增考点监控和备份(Monitor and Backup)
新增主题: 网络程序和容器(Web apps and containers)

Two ways to prepare 见页面下方官方文档

AZ-300的考试权重分配如下

  • 部署和配置基础设施 (25-30%)
  • 实现工作负载和安全 (20-25%)
  • 创建和部署 App (5-10%)
  • 实现认证和安全数据(5-10%)
  • 开发云和 Azure 存储 (20-25%)

在新的AZ-303中,这些权重将会有一些修改。

  • 考试中有50%的部分是关于部署和配置(Deploy and Configure)
  • 去掉了对认证和安全数据的要求
  • 去掉了对云开发:消息和自动扩展的要求
  • 新增部署数据平台的要求,包括SQL DB和Cosmos DB
  • 新增监控的要求 Monitoring

Two ways to prepare 见页面下方官方文档

AZ-301的考试权重分配如下

  • 确定工作量要求 (10-15%)
  • 设计身份和安全性 (20-25%)
  • 设计数据平台解决方案 (15-20%)
  • 设计业务连续性策略(15-20%)
  • 部署、迁移和集成设计(10-15%)
  • 设计基础设施策略(15-20%)

在新的AZ-304中,这些权重将会有一些修改。

  • 去掉获取信息和要求的部分
  • 去掉设计认证管理的部分
  • 去掉危险预防策略的部分
  • 去掉数据文档流的部分
  • 去掉数据保护策略的部分
  • 去掉数据监控策略的部分
  • 去掉存储策略的部分
  • 添加设计程序框架

Two ways to prepare 见页面下方官方文档

MaterialUI

1
yarn add @material-ui/core

override material ui styles

Next.js

典型的网站模块和框架,封装webpack、babel, 支持按需加载和seo优化

Redux

React提供了视图层面组件化开发的模式。为实现组件之间通信和多样的交互,需要引入Redux库

Redux is a predictable state container for JavaScript apps.

1
npm install @reduxjs/toolkit

store, state, action

一个应用中只有一个store,是所有组件数据的容器

1
2
3
4
import { createStore } from 'redux';
const store = createStore(fn);

const state = store.getState();

state是state在某个时间点的快照,state与view绑定

preact

据说是使用更符合Dom规范的事件系统,直接使用浏览器原生事件系统而不是统一用onChange,从而对React的设计进行了简化注1

craco

craco,当下流行的对React项目进行自定义配置的社区解决方案,AntDesign4官方亦有在使用
更骚的create-react-app开发环境配置craco
从create-react-app开始配置(关于create-react-app见React)

1
2
3
npx create-react-app my-project 
npx create-react-app my-project --template typescript
yarn add antd @craco/craco craco-less @babel/plugin-proposal-decorators babel-plugin-import -D

添加craco配置 craco.conf.js,即模块化配置,根据所需的资源参考方案:https://github.com/gsoft-inc/craco/tree/master/recipes

package.json scripts将命令替换为craco
  ”start”: “react-scripts start”,
  ”build”: “react-scripts build”,
  ”test”: “react-scripts test”,
  ”eject”: “react-scripts eject”

  ”start”: “craco start”,
  ”build”: “craco build”,
  ”test”: “craco test”

format.js 国际化

format.js

react router

安装react-router-dom,这个package是基于react-router开发的,且实现了现成的组件如Link Switch等

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
ReactDOM.render(
<BrowserRouter>
<AppRoutes />
</BrowserRouter>,
)
...

const AppRoutes = () => {
return (
<div>
<nav>
<ul>
<li><Link to="/dashboard">Dashboard</Link></li>
<li><Link to="/device">Device</Link></li>
<li><Link to="/squre">Squre</Link></li>
</ul>
</nav>
<div>
<Route path="/dashboard" component={Dashboard} />
<Route path="/device" component={Device} />
<Route path="/squre" component={Squre} />
</div>
</div>
)
}

拖拽 react-dnd

1
2
3
4
5
6
function DraggableComponent(props) {
const [collected, drag, dragPreview] = useDrag(() => ({
type,
item: { id }
}))
}

useDrag钩子, 接受一个specification配置 声明拖动的type 被拖动项item 需要回传的collect, 返回collect的属性,drag source的引用以及drag preview元素

1
2
3
4
5
6
7
8
const [{ isOver, canDrop }, dropRef] = useDrop({
accept,
drop: onDrop,
collect: (monitor) => ({
isOver: monitor.isOver(),
canDrop: monitor.canDrop(),
}),
})

useDrop钩子接收一个‘specification’配置作为参数 可配置drop-target接收的类型 需要回传的props参数
返回包含drop-target节点的reference以及回传的props如{isOver, canDrop}的一个列表

trouble shooting Can’t resolve ‘react/jsx-runtime’

vite.js

@emotion/react

css in js方案

在继承中闪耀的美德

灵魂之所以会伤害自身 往往都是它无法承受快乐或痛苦的时候

不以物喜,不以己悲,居庙堂之高,则忧其民;处江湖之远,则忧其君。是进亦忧,退亦忧;然则何时而乐耶?其必曰:“先天下之忧而忧,后天下之乐而乐”呼!噫!微斯人,吾谁与归?

理性的灵魂基于理性的生物

天地规律是我们必须遵守的

生前伟大 死后遗忘

与傻瓜对话我们即为傻瓜

做事不可迟缓,言谈不可杂乱,思想不可漂游,灵魂不可全然倾注自我,也不可总被外物烦扰,生活不可始终忙碌不止

为了人类的进步我应多做有意义的事

人与人之间,即相互鄙视,又相互奉承,每个人都希望自己高人一等,又匍匐在别人面前

马可·奥勒留在位的第5年是45岁,按现在的平均寿命而言他正值春秋鼎盛,这一年就是公元166年,他在这一年达成了养父安敦尼的政治遗愿——联通了中国。中国和罗马的史书都没有记载这次联通的结果,当时在位的皇帝是汉桓帝

奥勒留所在的历史时期命名为“安敦尼王朝”,这个时期的罗马帝国有6位皇帝,按次序分别是涅尔瓦、图拉真、哈德良、安敦尼、马可·奥勒留和康茂德。之所以如此称谓是因为其时的罗马统治者一般都认为,安敦尼的统治时期是罗马帝国最发达最繁荣的时代,并认为元首本人就是最理想君主的摹本,并把王朝中康茂德之前的五任皇帝评价为“五贤帝”,奥勒留是五贤帝的最后一位。

屋大维统治晚期,政治遗嘱建议罗马的疆域不再扩张,此后直到罗马分裂,其疆域一直保有着横跨欧亚非三个大陆的庞大体量。极度不发达的交通、极度辽阔的幅员和繁星满天的种族信仰所伴生的运转不灵兼供血不足,直接导致了巨人症帝国的统治矛盾丛生蔓长且无处不在。以奥勒留为例,如果形容他的统治期,最接地气的莫过于中国诗人王勃的16字咒“时运不济、命运多舛、冯唐易老、李广难封”。

奥勒留于162年继位成为罗马帝国皇帝。在前一年,帕提亚的沃洛吉斯四世(Vologases IV)继位,开始入侵亚美尼亚和叙利亚,重夺埃德萨,奥勒留的养父安敦尼在忧愤中去世,奥勒留“奉命于危难之间”,接过了充满了硝烟的权杖,而这场战争一直打到奥勒留特使抵达中国洛阳的那一年——罗马有心东来,中国却无暇西顾。

奥勒留的罗马处境远不止于此,在北方边境,马科曼尼人、夸狄人、萨马坦人、恺悌人和扎则基斯人等日耳曼部族都相继发动叛乱。巧合的还是公元166年——叛乱的先头部队一度冲进北部意大利。奥勒留当局被迫开启了全民皆兵模式,把奴隶和角斗士都编入军队。终奥勒留一生,这身扑火队长的行头一直没能脱下。180年3月17日,他死在了征讨马科曼尼人的多瑙河边的Pannonia省(现威尼斯),只是他并非战死而是死于瘟疫,而这场瘟疫也是奥勒留继位初期的种因。

奥勒留继位做的第一举措,就是邀请安敦尼的另一养子维鲁斯共理国事,这是罗马帝国史上首度出现两帝共治。随后罗马安息战争的东方战事,奥勒留就托付给维鲁斯处理,维鲁斯率兵攻陷并焚毁了塞琉西亚和泰西封,与此同时一些士兵染上莫名且致命的传染病,军队因此撤回国内。这些近东作战的士兵带来了天花和麻疹,大瘟疫序幕拉开。死亡之霾悄无声息地从小亚细亚半岛暴风一般席卷了帝国东部,旋即迅速蔓延到西部的意大利、高卢和日耳曼地区,罗马帝国所有的行省都无一幸免。两个共治皇帝均病殁于这次瘟疫。更具讽刺意味的是,这次史上称为“安东尼瘟疫”的传染病,正是以奥勒留(马可·奥勒留·安东尼·奥古斯都)的名字命名的。

这是传染病学史上的一次大事件,也是人类历史上十大恶性传染病事件之一。它继古希腊“雅典大瘟疫”之后,拜占庭“查士丁尼瘟疫”之前,为一千多年以后蔓延欧洲的黑死病风暴(即欧洲中世纪大瘟疫)开了先河。这场瘟疫,使包括社会精英在内的众多人口集体毙命。如雅典,在167-171年间,首席法官的职务就因候选人病死而无法补足。考虑到相似体制的行政单位还有许多,不少地方的基层治理能力就因此遭遇重创。罗马史学家迪奥卡称,当时罗马一天就有2千人因染病而死,占总传染人数的1/4;而在有些地方,瘟疫造成总人口的1/3死亡。估计总死亡人数有500万。

奥勒留当局不得不从普通奴隶或角斗士队伍中招募新兵。但老兵的安抚与新人的入职,无不需要大笔资金维持,而帝国的银矿开采也因瘟疫而陷入停顿,如东方商业重镇亚历山大港的银币铸造完全停止,商品价格普遍上涨而地租却巨幅下跌。梁实秋曾如此描述“民穷财困,局势日非,玛克斯(即马可·奥勒留)被迫出售私人所藏珠宝,筹款赈灾。此种困窘情形,在玛克斯在位之日,一直继续存在。内忧外患,交相煎迫。”这场瘟疫就像巨人泰坦的铁拳,将帝国的各级秩序敲得摇摇欲坠,几乎所有的行业均震荡备至。

这场瘟疫足足肆虐了7年之后才趋于消停。当在公元191年再度大规模爆发的时候,奥勒留已经去世11年了。

兵戈瘟疫之外也并非全部,还有洪水泛滥,此处不赘。穷其执政期间,奥勒留如同暴风雨中海上的一叶小舢舨,无时无刻不处在鞍马倥偬和颠踣动荡中。人类万物的恒性在于追求平衡,外面越是风雨雷电,内里越是风淡云清,否则必然走向崩溃和灭失。你既然理解太平天国运动中的曾国藩、宁王之乱中的王阳明,那你也必然会理解奥勒留为什么成了“哲学家皇帝”了。

今人不见古时月,今月曾经照古人。
古人今人若流水,共看明月皆如此。
唯愿当歌对酒时,月光长照金樽里。

行文不像书籍 主旨零散 更像是日记的随笔 深夜寂寞的灯下与自己内心的对话
我未伏案研读 肯定觉得枯燥 跑步时听的 在这种有氧运动中放空自己时 听到这样的文字 容易引起共鸣
你想这个跟你对话的是一千八百年前的‘古人’呀 而且还是皇帝 一位皇帝 高居庙堂 掌握着古罗马的超大版图 这种人物应该鲜有闲情逸致写这种散漫的文字
我们从经典古文中鲜有这种近距离之感

directives

1
2
3
4
pm2 start index.js
pm2 stop all
pm2 logs
pm2 delete all

env variables

将node.js应用封装成模块ecosystem.config.js (也可以直接用json)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
module.exports = {
apps : [
{
name: "myapp",
script: "./app.js",
watch: true,
env: {
"PORT": 3000,
"NODE_ENV": "development"
},
env_production: {
"PORT": 80,
"NODE_ENV": "production",
}
}
]
}

在启动命令时使用env_后面的字符串作为标识
1
pm2 start ecosystem.config.js --env production

pm2 plus

know more about pm2.io

开机自启

键入下面的命令生成startup脚本

1
pm2 startup

提示执行配置命令,如
1
2
[PM2] To setup the Startup Script, copy/paste >the following command:", 
sudo env >PATH=$PATH:/usr/local/bin /usr/local/lib/node_modules/pm2/bin/pm2 startup systemd -u username --hp /home/username

按照提示执行提示的命令, 执行后终端列出已安装的服务信息

Caution!升级nodejs对startup有影响

1
2
3
pm2 unstartup

pm2 startup

Azure AD

Windows2000 引入Active Directory作为identity provider和authorization database,可想而知,这个名称与其存储方式以及根据talent区分的文件结构之间的关系。随着Web应用的发展,有了云平台的Azure Active Directory,其主要功能之一仍是作为identity provider。

AD和Azure AD的结合实现了以本地Windows身份通过web实现SSO认证。

参考:《Is Azure AD an Identity Provider?》

Azure AD在office软件甚至其他Saas(Software as a service, 软件即服务)之间无缝访问,以及多重身份验证和条件访问控制

参考:使用 Azure Active Directory 进行应用程序管理

Azure AD, B2B, B2C Puzzled Out – What Makes The Difference?

B2B,对接Business和Business,使双方标识均可通过认证,主服务方持有访问权限的控制,

B2C, 面向customer 如下引述:

Azure Active Directory B2C provides business-to-customer identity as a service. Your customers use their preferred social, enterprise, or local account identities to get single sign-on access to your applications and APIs.

Azure Active Directory B2C 以服务的形式提供企业到客户的身份。 客户可使用其喜欢的社交、企业或本地帐户标识完成单点登录,访问应用程序和 API 。
azureadb2c-overview

“贴牌式身份验证解决方案” blabla

届时,访问DataService,跳转到如 CSDataServices.onmicrosoft.com/oauth2/v2.0/authorize?xxxx 格式的地址, 这是挂在Azure上的页面,可以做成本公司产品风格(见本文章节自定义登录页),sign in的form可以直接使用已注册(保存在Azure AD)的账号, 也可能提供了社交账号的链接,点击后跳转到社交平台登录页。
sign_in

Azure保存用户的标识,即使使用第三方的sso如公司的sso认证或社交账号,也会有将第三方凭据交换Azure标识的过程,该过程即典型的OAuth2

scenario-singlesignon

名称和概念

  • authority 颁发机构 形如 https://login.microsoftonline.com/tfp/{tenant}/{policyName}
  • tenantID 注册使用AAD 成为“tenant” 获得tenantID 由tenant name命名的子域名等
  • directory 存储所涉及的对象(如凭据,用户信息,配置)的物理或逻辑位置
  • Application registration 将自己公司产品注册为Azure AD B2C的App,以使用由Azure提供的贴牌认证
  • user flow 和 costom policy分别指基本的注册-登录-配置的流程以及自定义的策略
  • identity providers 第三方的标识提供方 如Facebook账号或Wechat账号授权服务

Azure ADB2C

Active Directory 的identity是在login.microsoftonline.com注册的,登录Azure portal也是同样的唯一的账号,B2C则提供了选择多个identity provider的功能,可以使用自己注册的tenant,抑或是社交账号,注册登录入口形如https://qqstudio.b2clogin.com/qqstudio.onmicrosoft.com/oauth2/v2.0/authorize
下面以官方sample为例配置,以求使用桌面客户端通过Azure AD B2C的认证框架访问Web Api

域服务(AD DS)和应用程序管理

即除了B2C之外的主要功能。AD DS见Azure域服务
AD可以用于管理Gallery App也就是微软库中的SaaS应用,也可以通过应用程序代理管理本地的应用(On-premises applications)
What does Azure AD Application Proxy do?
A.You use it to identify applications in your instance of Azure AD.
B.You use it to add on-premises applications to your instance of Azure AD.
C.You use it to add Azure AD Gallery applications to your instance of Azure AD.

创建资源

进入“创建资源”入口,搜索Azure AD B2C,选择创建—>给出两个选项,选择创建新的
micro docs
loading半天后提示创建成功,点击链接切换directory

Directory creation was successful. Click here to navigate to your new directory: QQStudio.

directory created

user flow

选择由Azure AD B2C控制的行为,一般就是登入登出,注册、注销,重置密码。

选择Sign up and sign in

输入user flow名称

选择email作为sign up的身份验证

选择需要收集的注册信息

关于reset password

使用本地帐户的 注册或登录 用户流在体验的第一个页面上包含“忘记了密码?”链接。 单击此链接不会自动触发密码重置用户流。
而是将错误代码 AADB2C90118 返回给应用程序。 应用程序需要通过运行一个可重置密码的特定用户流来处理此错误代码。 Microsoft Docs:user flow 概述

注册Api应用程序

将访问受控的应用(这里是Web Api)注册到Azure AD B2C,框架给予应用程序client id等标记,记下当登录成功时跳转回的地址————Redirect URI(关于Redirect URI的限制见本文Q&A部分)。

1
2
3
4
5
6
7
8
Display name:Demo website
Application (client) ID:c4b27029-a5ad-4022-979d-8721101df951
Directory (tenant) ID:9175ffa9-24b3-4fc1-806e-6d53582a7f4f
Object ID:c5064a61-0321-4a39-9f3c-dcef0df9b99c
Supported account types:All Microsoft users
Redirect URIs:1 web, 0 spa, 0 public client
Application ID URI:Add an Application ID URI
Managed application in local directory:Demo website

这里的Redireact URI是http://localhost:8888/auth,期望在本机运行桌面客户端程序,访问Api跳转到Azure Page登录,成功后进入到该地址。

进入管理—认证(Authentication),选择使用隐式授权流(Implicit grant, 见笔记OAuth2), 并添加Redirect Uri

04register_app_add_auth_url
进入管理—公开API(expose API),Application ID URI set 为https://qqstudio.onmicrosoft.com/api 默认是由GUID组成的

添加scope(Add a scope)
05add access scope
scope是控制访问权限的定义,将在后续步骤中被授权到已注册的client

配置Web Api应用的Authorization

使Web Api能将token拿到Azure AD B2C去校验,官方Sample中从config.js读取配置

1
2
3
4
5
6
7
8
9
10
const config = {
identityMetadata: "https://" + b2cDomainHost + "/" + tenantId + "/" + policyName + "/v2.0/.well-known/openid-configuration/",
clientID: clientID,
policyName: policyName,
isB2C: true,
validateIssuer: false,
loggingLevel: 'info',
loggingNoPII: false,
passReqToCallback: false
}

express web api的认证和重定向用到了passportpassport-azure-ad两个包,后者直接带入上面的config对象做为参数
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
const express = require("express");
const morgan = require("morgan");
const passport = require("passport");
const config = require('./config');
const BearerStrategy = require('passport-azure-ad').BearerStrategy;

const bearerStrategy = new BearerStrategy(config,
function (token, done) {
// Send user info using the second argument
done(null, {}, token);
}
);

const app = express();

app.use(morgan('dev'));
app.use(passport.initialize());

passport.use(bearerStrategy);

//enable CORS
app.use((req, res, next) => {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Authorization, Origin, X-Requested-With, Content-Type, Accept");
next();
});

// API endpoint
app.get("/hello",
passport.authenticate('oauth-bearer', {session: false}),
(req, res) => {
console.log('User info: ', req.user);
console.log('Validated claims: ', req.authInfo);

if ('scp' in req.authInfo && req.authInfo['scp'].split(" ").indexOf("demo.read") >= 0) {
// Service relies on the name claim.
res.status(200).json({'name': req.authInfo['name']});
} else {
console.log("Invalid Scope, 403");
res.status(403).json({'error': 'insufficient_scope'});
}
}
);

const port = process.env.PORT || 5000;

app.listen(port, () => {
console.log("Listening on port " + port);
});

注册客户端程序

将官网sample的wpf client注册到Azure AD B2C

创建完成后添加Api权限,或者说授权scope:管理—API权限(API Permission)—Add a permission—My APIs,选择已注册的Web API应用
06 add client api permissions
勾选Permissions,即上文中的scopes
07 select permissions
授权client使用scope:管理—API权限(API Permission)—Grant admin consent for xxxx(telent Name)—click Yes

QQs跟随sample的步骤遗漏了下面这一步————添加重定向地址————导致在配置客户端时Redirect Uri不知道填什么

管理—Authentication—Add a platform—Mobile and desktop applications 然后可以看到根据当前talent的user flow生成的登录页模板 勾选https://qqstudio.b2clogin.com/oauth2/nativeclient
08 client redirect to login page

配置客户端以访问已授权的Api

sample client是个WPF应用,引入了Microsoft.Identity.Client这个包来进行token校验

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
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System.IO;
using System.Text;
using System.Windows;
using Microsoft.Identity.Client;

namespace active_directory_b2c_wpf
{
public partial class App : Application
{
private static readonly string Tenant = "61874450-1725-44bb-bb8e-314575861ad6";
private static readonly string AzureAdB2CHostname = "https://qqstudio.b2clogin.com/oauth2/nativeclient";
private static readonly string ClientId = "8e039329-171a-4484-8151-4e67bf561218"; // 这里的clientId是WPF Client自己的ID
private static readonly string RedirectUri = "https://fabrikamb2c.b2clogin.com/oauth2/nativeclient"; // 这个是Azure给桌面客户端的登录页
public static string PolicySignUpSignIn = "B2C_1_basic_sign_up_and_sign_in";

public static string[] ApiScopes = { "https://qqstudio.onmicrosoft.com/api/demo.read", "https://qqstudio.onmicrosoft.com/api/demo.write" };
public static string ApiEndpoint = "https://fabrikamb2chello.azurewebsites.net/hello";
private static string AuthorityBase = $"https://{AzureAdB2CHostname}/tfp/{Tenant}/";
public static string AuthoritySignUpSignIn = $"{AuthorityBase}{PolicySignUpSignIn}";

public static IPublicClientApplication PublicClientApp { get; private set; }

static App()
{
PublicClientApp = PublicClientApplicationBuilder.Create(ClientId)
.WithB2CAuthority(AuthoritySignUpSignIn)
.WithRedirectUri(RedirectUri)
.WithLogging(Log, LogLevel.Info, false) // don't log PII details on a regular basis
.Build();

TokenCacheHelper.Bind(PublicClientApp.UserTokenCache);
}
private static void Log(LogLevel level, string message, bool containsPii)
{
string logs = ($"{level} {message}");
StringBuilder sb = new StringBuilder();
sb.Append(logs);
File.AppendAllText(System.Reflection.Assembly.GetExecutingAssembly().Location + ".msalLogs.txt", sb.ToString());
sb.Clear();
}
}
}

scopes

通过scopes管理对受保护资源的权限,请求令牌时,客户端传递scope

关于校验和跳转的包的实现的推测

  • 客户端访问api,Http/Https Request
  • 客户端Request使用Jwt Bearer Authentication 传递token
    10 bearer auth
  • 服务端接收到的request中token缺少或过期,返回401
  • 客户端收到401打开Azure Sign in Page,附带重定向回api end point 的url
  • Azure AD 框架进行认证
  • Azure AD 框架查询并授权 颁发相应的token
  • 客户端接收到token并缓存

    自定义策略用户流

    使用ADB2C认证授权流可选择预置的User flow(见上文)或自定义策略

Microsoft Docs:为 Identity Experience Framework 应用程序添加签名和加密密钥

A Walkthrough For Azure AD B2C Custom Policy (Identity Experience Framework)

下载新手配置包(starterpack

  • 使用Azure AD 作为identity provider(存目)

    以实现一键(使用AD凭据)登录

    对接wechat 作为identity provider(存目)

    在user flow - identity provider中勾选社交账号 wechat

    关于系统角色定义

    多个系统使用Azure AD B2C,各个系统地权限角色是否要在Azure方维护呢?是否是在expose API时定义scope呢?

    私以为并不是,鉴于OAuth一篇中所述,资源服务器保留私钥对access token进行校验,甚至可以从中解析出当前用户key,过期时间等信息,籍此完全可以查询本系统定义地权限角色,而无须频繁访问SSO。

    Q&A

    issue: The application associated with client id has no registered redirect URIs.

按说在App Registry中配置 Redirect URI是optional的,曾遇到此问题是没有勾选隐式授权(Authentication—>Implicit grant)

一定需要注册Redirect URI吗,可以在跳转到登录页时作为query parameter传递吗?

一定要注册, 似乎是出于复杂的安全性的考虑 见StackOverflow:Why is Redirect URL Fully Qualified in Azure AD B2C?
跳转到登录页时确实会传递Redirect_URI参数,否则会报redirect_uri_mismatch的Error且不会传回access token
Redirect Uri 的限制要求作为跳转参数的Redirect_URI,与注册在ADB2C上的若干Redirect URIs之一完全匹配,除了localhost(匹配时自动忽略端口)。
官方文档还提到了state参数,跳转参数state将在登录成功后链在Redirect URI后面,可以用来恢复跳转登录前的浏览状态

Silent Sign In Workflow

自定义登录页

参考Microsoft Docs: ADB2C UX自定义
可设置蓝色 灰色 经典风格的sign in页面 以及公司logo
设置表单项目及排序等

Wechat

首先是在微信公众平台注册网站应用,注册过程需要填写企业/个人网站的官网和备案号,很头疼

添加Wechat为AB B2C的identity provider:Azure AD B2C —> Identity providers —> WeChat(Preview) 可以看到Callback URL(填到微信公众平台上注册的网站应用的授权回调域配置中),Name可以填WeChat,填写网站应用的id/secret

添加identity provider到User flow:Azure AD B2C —> User flows —> [Your User flow] —> Identity providers 选择已添加到AD B2C的 social identity provider

多个资源

issue: MSAL AADB2C90146 ‘Openid profile’ provided in request specifies more than one resource for an access token, which is not supported’

stackoverflow: use requestsilent

Tips

JSX模板

以.jsx为后缀的标记文件,用以书写混合js逻辑的dom模板
example.jsx, jsx文件经Babel编译为js运行

1
2
3
4
5
6
7
const JSX = (
<div className="myDiv">
<h1>Hello World</h1>
<p>Lets render this to the DOM</p>
</div>
);
ReactDOM.render(JSX,document.getElementById("challenge-node"))

  • 用{}包含js代码,包括变量和相应方法
  • 用className绑定class样式

    注释

    use the syntax {/ /}

    表单

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    render() {
    return (
    <form onSubmit={this.handleSubmit}>
    <label>
    文章:
    <textarea value={this.state.value} onChange={this.handleChange} />
    </label>
    <input type="submit" value="提交" />
    </form>
    );
    }

    ES6语法

    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
    import { Component } from 'react'

    class App extends Component {
    constructor(props) {
    super(props);
    }
    render() {
    return (
    <div>
    <h1>Here is Entry</h1>
    {/* 子组件 */}
    <ChildComponent />
    </div>
    );
    }
    };

    const ChildComponent = () => {
    return (
    <div>
    <p>I am the child</p>
    </div>
    );
    };

    ReactDOM.render(<App />, document.getElementById("app"))

    子组件传参

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    const CurrentDate = (props) => {
    return (
    <div>
    <p>The current date is: {props.date}</p>
    </div>
    );
    };
    const Data = ()=>{
    return new Date().toLocaleString();
    }
    class Calendar extends React.Component {
    constructor(props) {
    super(props);
    }
    render() {
    return (
    <div>
    <h3>What date is it?</h3>
    <CurrentDate date={Date()}/>
    <p>{this.props.name}</p>
    </div>
    );
    }
    };
    使用函数表达式不需要this指针而class定义是要的(ES6 ()=>{}不创建this)

另外设置默认参数:ComponentA.defaultProps = {name:’New Component’}

更多组件通信见React组件交互

参数校验

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
MyComponent.propTypes = {
// 你可以将属性声明为 JS 原生类型,默认情况下
// 这些属性都是可选的。
optionalArray: PropTypes.array,
optionalBool: PropTypes.bool,
optionalFunc: PropTypes.func,
optionalNumber: PropTypes.number,
optionalObject: PropTypes.object,
optionalString: PropTypes.string,
optionalSymbol: PropTypes.symbol,

// 任何可被渲染的元素(包括数字、字符串、元素或数组)
// (或 Fragment) 也包含这些类型。
optionalNode: PropTypes.node,

// 一个 React 元素。
optionalElement: PropTypes.element,

// 一个 React 元素类型(即,MyComponent)。
optionalElementType: PropTypes.elementType,

// 你也可以声明 prop 为类的实例,这里使用
// JS 的 instanceof 操作符。
optionalMessage: PropTypes.instanceOf(Message),

// 你可以让你的 prop 只能是特定的值,指定它为
// 枚举类型。
optionalEnum: PropTypes.oneOf(['News', 'Photos']),

// 一个对象可以是几种类型中的任意一个类型
optionalUnion: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
PropTypes.instanceOf(Message)
]),

// 可以指定一个数组由某一类型的元素组成
optionalArrayOf: PropTypes.arrayOf(PropTypes.number),

// 可以指定一个对象由某一类型的值组成
optionalObjectOf: PropTypes.objectOf(PropTypes.number),

// 可以指定一个对象由特定的类型值组成
optionalObjectWithShape: PropTypes.shape({
color: PropTypes.string,
fontSize: PropTypes.number
}),

// An object with warnings on extra properties
optionalObjectWithStrictShape: PropTypes.exact({
name: PropTypes.string,
quantity: PropTypes.number
}),

// 你可以在任何 PropTypes 属性后面加上 `isRequired` ,确保
// 这个 prop 没有被提供时,会打印警告信息。
requiredFunc: PropTypes.func.isRequired,

// 任意类型的数据
requiredAny: PropTypes.any.isRequired,

// 你可以指定一个自定义验证器。它在验证失败时应返回一个 Error 对象。
// 请不要使用 `console.warn` 或抛出异常,因为这在 `onOfType` 中不会起作用。
customProp: function(props, propName, componentName) {
if (!/matchme/.test(props[propName])) {
return new Error(
'Invalid prop `' + propName + '` supplied to' +
' `' + componentName + '`. Validation failed.'
);
}
},

// 你也可以提供一个自定义的 `arrayOf` 或 `objectOf` 验证器。
// 它应该在验证失败时返回一个 Error 对象。
// 验证器将验证数组或对象中的每个值。验证器的前两个参数
// 第一个是数组或对象本身
// 第二个是他们当前的键。
customArrayProp: PropTypes.arrayOf(function(propValue, key, componentName, location, propFullName) {
if (!/matchme/.test(propValue[key])) {
return new Error(
'Invalid prop `' + propFullName + '` supplied to' +
' `' + componentName + '`. Validation failed.'
);
}
})
};

使用 PropTypes 进行类型检查

state及组件生命周期

props是父组件传入的只读参数,state是组件自身的动态的状态

为了正确地构建组件,需要找出组件模型所需的 state 的最小表示,其他所有数据根据该state计算出。React哲学
props是传入参数,而state是组件内部表征状态的对象,往往在构造函数中,根据props初始化state

组件状态更新使用setState,函数触发组件的重新渲染

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
class Clock extends React.Component {
constructor(props) {
super(props);
// 初始化date为当前时间
this.state = {date: new Date()};
}
/* 加载 */
componentDidMount() {
// 1s后开始跳
this.timerID = setInterval(
() => this.tick(),
1000
);
}
/* 卸载 */
componentWillUnmount() {
clearInterval(this.timerID);
}
tick=()=>{this.setState((pre)=>({date:new Date()}))}

render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}

bind ‘this’

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
text: "Hello"
};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState({
text: "You clicked!"
});
}
render() {
return (
<div>
<button onClick={this.handleClick}>Click Me</button>
<h1>{this.state.text}</h1>
</div>
);
}
};

条件渲染

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
render() {
const isLoggedIn = this.state.isLoggedIn;
let button;
if (isLoggedIn) {
button = <LogoutButton onClick={this.handleLogoutClick} />;
} else {
button = <LoginButton onClick={this.handleLoginClick} />;
}

return (
<div>
<Greeting isLoggedIn={isLoggedIn} />
{button}
</div>
);
}

与运算&&

1
{flag && <toggleComponent />}

三目运算
1
2
3
4
5
6
7
8
9
10
render() {
const isLoggedIn = this.state.isLoggedIn;

return (
<div>
<Greeting isLoggedIn={isLoggedIn} />
{isLoggedIn?<LogoutButton onClick={this.handleLogoutClick} />:<LoginButton onClick={this.handleLoginClick} />}
</div>
);
}

继承

没有继承!

在render return中组合子组件提供了清晰而安全地定制组件外观和行为的灵活方式,没有需要使用继承来构建组件层次的情况。React Docs:组合 vs 继承

createRef onRef useRef

子组件实例化回调函数,用以获取子组件对象
useRef 仅能用在 FunctionComponent,createRef 仅能用在 ClassComponent。

组件的对象会随组件的更新而刷新,但useRef返回的对象不会随着组件的更新而重新构建,像引入的全局变量,且随组件的销毁而释放,不需手动销毁

Fragment

相当于Angular的template,插入子组件不生成额外的元素(如div),可以省略为<>

ReactDOMServer

1
2
3
4
5
6
7
8
9
class App extends React.Component {
constructor(props) {
super(props);
}
render() {
return <div/>
}
};
ReactDOMServer.renderToString(<App />);

服务端渲染(SSR),生成dom的html字符串,实现SEO优化

Create React App

这是一个package create-react-app, 如angular-cli,和vue-cli中包含的命令工具(这里封装的命令是react-scripts, 见package.json中的scripts),用以创建基于React的完整应用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
my-app/
node_modules/
public/
index.html
favicon.ico
src/
App.css
App.js
App.test.js
index.css
index.js
logo.svg
README.md
package.json

关于typescript

1
yarn add --dev typescript

在tsconfig.json中配置typescript编译器()

使用tsx文件书写包含jsx代码的typescript代码

使用tsc命令编译

添加@types/react, vs code 右下角TypeScript版本选择4..-pnpify

tourial

安装dotnet ef cli

1
dotnet tool install --global dotnet-ef

创建‘迁移’

1
dotnet ef migrations add QQsInitialCreate

issue: No project was found. Change the current working directory or use the —project option.

项目入口(startup)的csproj与models目录分离,如

1
2
3
4
├───MyProduct.API
│ └───startup.cs
└───MyProduct.Models
└───migrations

定位startup project:
1
2
cd MyProduct.Models
dotnet ef migrations add QQsInitialCreate --startup-project "D:\QQsWorkspace\MyProduct.API"

更新数据库(—startup-project参数略):
1
dotnet ef database update

参考 Microsoft Docs:Entity Framework Core 工具