0%

配置和管理虚拟网络占比30-35%
目标导向:

  • 学习创建虚拟网络
  • 创建虚拟VPN网关
  • 认识使用ExpressRouter

    虚拟网络

    功能:
  • 隔离和分割(Isolation & segmentation)
  • 网络通信、资源间通信、与本地网络(on-premises)通信
  • 连接虚拟网络
  • 路由|筛选|连接网络流量(route|filter network traffic)

VPN(virtual private networks):

  • Point-to-site
  • Site-to-site
  • Azure ExpressRoute

    Network Monitor

  • 监视vm与endpoint(可以是其他vm)之间的通信
  • 查看vnet中的资源及其关系
  • 诊断(Diagnose)出入vm的网络流量筛选问题
  • 诊断vm网络路由问题
  • 诊断vm出站连接(outbound connections)
  • 捕获出入vm的数据包
  • 诊断vnet网关与连接的问题
  • 检查区域与internet相对延迟
  • 查看安全规则

    概念

    IP地址空间:举个栗子地址空间192.168.1.0/24,子网掩码255.255.255.248。子网掩码用来指明某个IP地址哪些位是网络位,哪些是主机位,同网络位IP之间的通信不需要通过网关,主机位数值就是有多少主机。IP总共32位,‘/24’是指前24位都是网络位,主机坐在的网络,248即11111000,这个网络有可以有 25 即32个子网,每个子网可分配地址为 23 - 2(减去广播地址和网络地址),为6个

    虚拟机通过虚拟网络通信的实践

    创建虚拟网络,名为default
    1
    2
    $Subnet=New-AzVirtualNetworkSubnetConfig -Name default -AddressPrefix 10.0.0.0/24
    New-AzVirtualNetwork -Name myVnet -ResourceGroupName vm-networks -Location $Location -AddressPrefix 10.0.0.0/16 -Subnet $Subnet
    使用powershell创建两个Azure VM
    1
    2
    3
    4
    5
    6
    7
    New-AzVm `
    -ResourceGroupName "vm-networks" `
    -Name "testvm1" `
    -VirtualNetworkName "myVnet" `
    -SubnetName "default" `
    -image "Win2016Datacenter" `
    -Size "Standard_DS2_v2"
    *取消其中一台的公共IP
    1
    2
    3
    $nic = Get-AzNetworkInterface -Name testvm2 -ResourceGroup vm-networks
    $nic.IpConfigurations.publicipaddress.id = $null
    Set-AzNetworkInterface -NetworkInterface $nic
    使用PublicIP远程VM1,在VM1使用计算机名访问同一虚拟网络的VM2

VPN网关

Azure虚拟网关为‘从本地到Azure’的传入连接提供一个endpoint,VPN网关是一种虚拟网关类型,可以作为加密的endpoint,在Azure的实践中,VPN网关用以在不同区域之间安全地链接虚拟机和服务

将图标转为字体格式

icomoon

将图标封装到css伪类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
.icon.icon-add{
position: relative;
padding-left: 22px;
margin-left: 4px;
&::before{
content: '';
position: absolute;
left: 0px;
bottom: calc(50% - 10px);
background: url(./assets/image/icon_add.svg) no-repeat top left;
width: 18px;
height: 18px;
}
}

通常调用一个功能,将其所属类型new出一个实例,随后调用这个对象的方法,new的过程使用具体的构造方法,后来我们将接口和实现分离,调用功能的地方用接口编码,而具体实现的方法在某处独立配置,一旦需要修改实现,不至于到处替换代码。

ASP.NET Core提供内置服务容器IServiceProvider,将服务注入到使用它的类的构造函数中。 框架负责创建依赖关系的实例,并在不再需要时将其释放。

注册服务

1
2
3
4
5
6
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IDateTime, SystemDateTime>();

services.AddControllersWithViews();
}

其中IDateTime是接口类,SystemDateTime是前者的实现类,AddSingleton(IServiceCollection, Type)方法将 serviceType 中指定类型的单一实例服务添加到指定的 IServiceCollection 中。
以生命周期区分的三种方式还有services.AddScoped()的注册方式以及services.AddTransient()方式, 区别如下

  • singleton: IoC容器将在应用程序的整个生命周期内创建和共享服务的单个实例。
  • transient:每次您请求时,IoC容器都会创建一个指定服务类型的新实例。
  • scope: IoC容器将为每个请求创建一次指定服务类型的实例,并将在单个请求中共享。

    构造函数注入

    调用接口时,将接口类作为构造函数参数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class Index2Model : PageModel
    {
    private readonly IMyDependency _myDependency;
    public Index2Model(IMyDependency myDependency)
    {
    _myDependency = myDependency;
    }
    public void OnGet()
    {
    _myDependency.WriteMessage("Index2Model.OnGet");
    }
    }

    FromServices attribute注入

    1
    2
    3
    4
    public IActionResult About([FromServices] IDateTime dateTime)
    {
    return Content( $"Current server time: {dateTime.Now}");
    }

    Controller settings注入

    setttings类
    1
    2
    3
    4
    5
    public class SampleWebSettings
    {
    public string Title { get; set; }
    public int Updates { get; set; }
    }
    将Configuration类加入到services collection
    1
    2
    3
    4
    5
    6
    7
    public void ConfigureServices(IServiceCollection services)
    {
    services.AddSingleton<IDateTime, SystemDateTime>();
    services.Configure<SampleWebSettings>(Configuration);

    services.AddControllersWithViews();
    }

Caution! 这个东西非常昂贵 不要在自己的Azure上学习实践!

Azure AD DS

域名(domain name),是命名空间,域名用于在网络传输中标识资源的电子方位。

Azure Active Directory 域服务 (AD DS) 提供托管域服务,如域加入(domain join),group policy,轻量目录访问协议(lightweight directory access protocol,LDAP)等。无须在云端手动部署和管理domain controllers即可使用上述服务

创建托管域

Azure Portal上搜索并创建Azure AD Domain Services

注意事项

  • 内置域名(Built-in domain name) 以 .onmicrosoft.com 为后缀的内置域名
  • 自定义域名,指定自定义域名,通常是你已拥有且可路由的域名
  • 前缀限制 15字符
  • 网络名称冲突: 托管域的 DNS 域名不能已存在于虚拟网络中。
    确认创建后便开始漫长的等待部署的过程

    后续步骤

  • 更新虚拟网络DNS
  • “forest(林)”

SSDT Project

SQL Server Data Tools (SSDT) 通过引入跨 Visual Studio 内所有数据库开发阶段的无所不在的声明性模型,为数据库开发带来变革。创建一个数据库项目进行脱机的数据库开发(不直接对数据库服务进行在线修改),像编辑声明定义一样创建、编辑、重命名和删除表、存储过程、类型和函数。

为Visual Studio安装SSDT的features,加载sqlproj或dtproj项目(VS2010数据库和服务器项目、数据层应用程序项目),用所提供的方法build、publish所作的修改。

参考Microsoft Docs:面向项目的脱机数据库开发

创建visual studio sql project

参考SQL Server 数据库项目

publish

右键项目 选择Publish
注意 Scripts目录下的脚本也会执行,因此在这里编写初始数据是可行的
如果是初始化数据,为了防止脚本重复执行,可以按如下方式插入数据

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
DECLARE @CSDid uniqueidentifier
SELECT @CSDid=NEWID()
INSERT INTO [ent].[ScannerGroup]
([ID]
,[Name]
,[ParentID]
,[Type]
,[BrandID]
,[GroupLevel])
SELECT
@CSDid
,'CSD'
,null
,10
,null
,HierarchyID::GetRoot()
WHERE NOT EXISTS (SELECT 1 FROM [ent].[ScannerGroup])

INSERT INTO [ent].[PartnerAdmin]
([ID]
,[Email]
,[GroupID]
,[Name])
SELECT
NEWID()
,'qqqqq@qqqq.qqq'
,@CSDid
,'QQs'
WHERE NOT EXISTS (SELECT 1 FROM [ent].[PartnerAdmin])
GO

Predeployment Scripts & Postdeployment Scripts

Predeployment Scripts和Postdeployment Scripts分别在数据库项目生成的主要部署脚本之前和之后执行,在 Visual Studio 中,从架构比较结果更新目标时(Compare之后的Update),将不执行Predeployment Scripts。
一个项目只能有一个Predeployment Scripts和一个Postdeployment Scripts。

Scripts文件夹右键Add —> Script… 选择Pre Deployment Scripts或Post Deployment Scripts

issues

Only one statement is allowed per batch. A batch separator, such as ‘GO’, might be required between statements.

Scripts目录下的sql文件属性中,默认Build Action = Build导致编译失败,应改为Build Action = None

.jfm文件,可以认为是对项目操作的备份,若未自动添加到gitignore,则可手动添加

Cannot import the following key file: RightCheckDB.pfx. The key file may be password protected. To correct this, try to import the certificate again or manually install the certificate to the Strong Name CSP with the following key container name: VS_KEY_E7D8A7C85598CE59

pfx是保存SSL Certificate的一种文件格式,上述错误表示SQL项目需要重新认证凭据。
重新写入密码的方式

1
"C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8 Tools\sn.exe" -i companyname.pfx VS_KEY_3E185446540E7F7A

用MSBuild和Jenkins实现Continuse Integration

1
2
3
## set msbuild.exe=C:/Program Files (x86)/Microsoft Visual Studio/2017/Professional/MSBuild/15.0/Bin/MSBuild.exe
msbuild.exe /t:Build "MyDB.sqlproj"
msbuild.exe /t:Publish /p:SqlPublishProfilePath="MyDB.publish.staging.xml" "MyDB.sqlproj"

DevOps issues

  • 实现一键部署
  • 实现集成过程可配置
  • 自动化脚本提交到版本控制库
  • 利用版本控制回退数据库
  • 应用的更新的集成使用最新的数据库
  • pipeline执行测试

包如其名,是对openID connection协议的实现

包主要提供基于两个class的功能,UserManager和OidcClient。UserManager提供登录/登出,管理OIDC provider返回的用户信息(user claims),管理access token等,是oidc-client package的主要功能。OidcClient是更底层的协议实现,并被UserManager调用。

UserManager初始化

UserManager使用OIDC provider的相关配置进行初始化

1
2
3
4
5
6
7
8
9
10
11
readonly setting = {
client_id: "c5a333eb-fbbd-4643-b8fb-846e0c82ca03", // Required!
authority: "https://qqstudio.b2clogin.cn/qqstudio.onmicrosoft.com/B2C_1_pp_userflow/v2.0/", // 即{tenant}/{directory}{userflow} Required!
response_type: "id_token token", // string, default: 'id_token' Required!
scope: "openid" // Required!OIDC provider授权的scope
redirect_uri: "https://localhost:44362/signin-callback.html", // 返回的token将被链在这个url后面 Required!

silent_redirect_uri: "https://localhost:44362/silent-callback.html",
automaticSilentRenew: true
}
var _userManager = new UserManager(settings);

其他参数见oidc-client wiki

跳转到登录

当access_token缺失或过期,抑或由api发现失效而返回401时,可以调用UserManager的下述方法跳转到登录

1
2
3
4
5
this._userManager.signinRedirect({ state: location.href, prompt: "login" }).then(function () {
console.log('signinRedirect done');
}).catch(function (err) {
console.log(err);
});

方法支持传键值对作为参数,这些参数将拼接在登录地址的url parameter中形如&prompt=login,登录成功重定向回到app时可以取回这些参数,从而继续被登录中断的操作。

重定向回到App

在signin-callback.html页面的初始化方法中调用userManager的signinRedirectCallback,该方法从url的parameter取出access token以及前文所述的其他登录跳转参数(这些参数可以用回调函数取出处理)

1
2
3
4
5
6
7
this._userManager.signinRedirectCallback((user: User) => {
if (user && user.state) {
this.router.navigateByUrl(user.state).then(b => {
console.log("navigate to url", user.state, " return", b);
})
}
});

实际上callback url后带的是形如&state=b2983fd692b94528852297782ef93bbf的参数,推测此为指向原value的标记,原value存于localstorage

User

调用getUser方法取用户信息, 部分信息是从access token中解析得出,部分可能需要配置其他workflow比如profile

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
class User {
constructor(settings: UserSettings);

/** The id_token returned from the OIDC provider */
id_token: string;
/** The session state value returned from the OIDC provider (opaque) */
session_state?: string;
/** The access token returned from the OIDC provider. */
access_token: string;
/** Refresh token returned from the OIDC provider (if requested) */
refresh_token?: string;
/** The token_type returned from the OIDC provider */
token_type: string;
/** The scope returned from the OIDC provider */
scope: string;
/** The claims represented by a combination of the id_token and the user info endpoint */
profile: Profile;
/** The expires at returned from the OIDC provider */
expires_at: number;
/** The custom state transferred in the last signin */
state: any;

toStorageString(): string;
static fromStorageString(storageString: string): User;

/** Calculated number of seconds the access token has remaining */
readonly expires_in: number;
/** Calculated value indicating if the access token is expired */
readonly expired: boolean;
/** Array representing the parsed values from the scope */
readonly scopes: string[];
}

调用removeUser 清除localstorage中的登录信息 非常的方便~

知乎:什么是微服务

参考 微服务的演进
单体服务
单体服务
垂直分层架构
垂直分层架构

微服务是一种小型的SOA架构(Service Oriented Architecture 面向服务的架构),其理念是将业务系统彻底地组件化和服务化,形成多个可以独立开发、部署和维护的服务或者应用的集合,以应对更快的需求变更和更短的开发迭代周期。

拓展:传统SOA使用ESB(Enterprise Service Bus 企业服务总线)进行各业务系统间的通信

目的或优点:

  • 服务模块解耦
  • 团队分工更容易,更明确, 技术栈异构
  • 独立部署,可针对独立模块进行发布 更快的迭代
  • 扩展能力强 不至于牵一发动全身
    相应的缺点是服务划分的困扰,系统复杂化,实施部署、纠错的难度增大等

“无非是业务拆分和基架体系搭建”

SOA

Service Oriented Ambiguity 即面向服务架构

SOAP(web service) http+xml
REST http+json
RPC socket

oidc-client

oidc-client

Passport.js

Passport.js是Express.js的中间件,
Azure Samples中的node.js API active-directory-b2c-javascript-nodejs-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
const passport = require("passport");
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(passport.initialize());
passport.use(bearerStrategy);

// 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'});
}
}
);

栗子中该中间件将认证失败的请求拦截住并返回401, 省略了web api授权失败时的重定向配置,私以为可以配置signin重定向,使得可以从浏览器访问api

MSAL.net

Microsoft Authentication Library(微软身份认证库MSAL),在ASP和SPA一文中有引用。

The Microsoft Authentication Library for JavaScript enables client-side JavaScript web applications, running in a web browser, to authenticate users using Azure AD. MSAL.js用以浏览器中运行的js web 使用Azure AD认证

上述是基于msal.js的JavaScript Packages, 对于客户端应用,微软提供.net framework运行时环境的一套SDK:

MSAL for .NET, UWP, NetCore, MAUI, Xamarin Android and iOS

The MSAL library for .NET is part of the Microsoft identity platform for developers (formerly named Azure AD) v2.0. It enables you to acquire security tokens to call protected APIs. It uses industry standard OAuth2 and OpenID Connect. The library also supports Azure AD B2C.

官方文档

我们关注使用WPF客户端打开登录ADB2C认证页面的应用场景
MSAL.NET使用Web浏览器

MSAL.NET 是一个多框架库,它具有特定于框架的代码,可在 UI 控件中托管浏览器(例如,在 .NET Classic 中,它使用 WinForms;在 .NET 5.0+ 中,它使用 WebView2;在 Xamarin 中,它使用本机移动控件,等等)。 此控件称为 embedded Web UI。 另外,MSAL.NET 还能够启动系统 OS 浏览器。

1
Install-Package Microsoft.Identity.Client -Pre
1
2
3
4
5
6
7
using Microsoft.Identity.Client;
...

IPublicClientApplication publicClientApp = PublicClientApplicationBuilder.Create(ClientId)
.WithRedirectUri("https://login.microsoftonline.com/common/oauth2/nativeclient")
.WithAuthority(AzureCloudInstance.AzurePublic, Tenant)
.Build();

使用自定义交互界面, 调用 WithCustomWebUi() 方法 传入自定义页面的实例,自定义页面类需要实现ICustomWebUi接口,接口定义了异步方法 AcquireAuthorizationCodeAsync,该方法参数

  • authorizationUri Uri
    URI computed by MSAL.NET that will let the UI extension navigate to the STS authorization endpoint in order to sign-in the user and have them consent

  • redirectUri Uri The redirect URI that was configured. The auth code will be appended to this redirect URI and the browser will redirect to it.

Public Client Application 和 Confidential Client Application: Confidential Client Application用于服务端应用,不会轻易访问到,使用client_secret标识身份;Public Client Application 运行在桌面或移动设备,保存client_secret是不安全的,因此凭借用户的credentials访问API

use WAM

Web 帐户管理器 (Web Account Manager) windows10提供的认证账户保存组件

  • Enhanced security. See Token protection. 关联客户端密码和token的加密
  • Support for Windows Hello(是使用 PIN、面部识别或指纹来快速访问 Windows的入口), Conditional Access, and FIDO keys(Fast IDentity Online Keys 在线密钥对).
  • Integration with the Windows Email & accounts view.
    Fast single sign-on.
  • Ability to sign in silently with the current Windows account.
  • Bug fixes and enhancements shipped with Windows.

OData协定

Open Data Protocol(开放数据协议,OData)是用来查询和更新数据的一种Web协议,其提供了把存在于应用程序中的数据暴露出来的方式。OData运用且构建于很多Web技术之上,比如HTTP、Atom Publishing Protocol(AtomPub)和JSON,提供了从各种应用程序、服务和存储库中访问信息的能力。OData被用来从各种数据源中暴露和访问信息,这些数据源包括但不限于:关系数据库、文件系统、内容管理系统和传统Web站点。

OData解决的是Restful Api定义太随便的问题
比如查询人员
A团队的API 可能是这样:http://A/api/Users/001
B团队的API 可能是这样:http://A/api/Users?id=001
返回结果也可能各有加工,{status: “ok”,data: {…}}的设计实际上只是自我感觉良好

OData则约束调用接口 Users
然后返回值形如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"@odata.context": "https://localhost:44346/odata/v1/$metadata#Users",
"value": [
{
"name": "QQs",
"emailAddress": "qqs@qqs.qqs",
"logo": "img base 64",
"enable": true,
"addressId": "ca35902d-d236-4496-b649-2f7fcf531110",
"recordId": "c7612b51-ddb2-eb11-9bf6-180373e9ce7d",
"recordCreated": "2021-05-12T05:52:19.979+08:00",
"recordLastUpdated": "2021-05-12T04:48:39.377+08:00"
}
]
}

返回值中包含接口的数据上下文文档,包含属性类型定义,Users 相关Api的参数设计等
OData似乎暴露了过多的底层设计的,而这么做的目的和结果是对数据的访问不再受限于定义各种查询、过滤以及分页API(由OData库依照标准实现类似sql的查询方式)
在.net开发中,得益于entityframework强大的关联模型设计和limbda查询方式,OData方显强大

参考OData.org Getting started

安装和应用OData Library

Nuget Package Manager 安装Microsoft.AspNetCore.OData Caution! 这里是7.0版本
添加Model DBContext Controller(这块参考EntityFramework)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddOData();
}

public void Configure(IApplicationBuilder app)
{
var builder = new ODataConventionModelBuilder(app.ApplicationServices);

builder.EntitySet<Product>("Products");

app.UseMvc(routeBuilder =>
{
// and this line to enable OData query option, for example $filter
routeBuilder.Select().Expand().Filter().OrderBy().MaxTop(100).Count();

routeBuilder.MapODataServiceRoute("ODataRoute", "odata", builder.GetEdmModel());

// uncomment the following line to Work-around for #1175 in beta1
// routeBuilder.EnableDependencyInjection();
});
}

参考QQs TeamsPersistanceCenter项目

filter

  • $filter=name eq ‘QQstone’
  • $filter=type ne ‘standard’
  • $filter=contains(email, ‘163.com’)
  • $filter=quantity gt 1 and le 100
    gt(greater than) lt(less than) ge(greater than or equal to) le(less than or equal to)
    注意单引号

    select

  • $select=name,email,type

    expand

    关联查询 $expand=department

    关于分页

    https://localhost:44346/odata/v1/Users?$count=true

https://localhost:44346/odata/v1/Users?$skip=20&$top=10

trouble shooting

issue: The EntityXXX key in the URL xxxx does not match the key (00000000-0000-0000-0000-000000000000) in the request content body.

reason:put method, post body中也要包含主键 xxxx

flaw

过分暴露底层模型设计, 也许像GraphQL一样,需要在接口层以下封装一下,微软也不是什么都没做,EDM了解一下

EDM

The Entity Data Model, or EDM, is the abstract data model that is used to describe the data exposed by an OData service.

The EDM makes the stored form of data irrelevant to application design and development.

EF提供了对象描述数据结构的概念 EDM使应用(上层)设计和开发与存储形式无关
更正 EDM就是Model层映射了实体数据的class,也就是之前说的‘entity’ 于封装数据木有直接关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 private static IEdmModel GetEdmModel()
{
var builder = new ODataConventionModelBuilder();
builder.EnableLowerCamelCase();
builder.EntitySet<Partner>(GetEntitySetName<PartnersController>());
var entityType = builder.EntitySet<Partner>(GetEntitySetName<PartnersController>()).EntityType;

entityType.Collection.Function(nameof(PartnersController.GetPartnersBySN))
.ReturnsCollectionFromEntitySet<Partner>("Partners")
.Parameter<string>("serialNo");

builder.EntitySet<Member>(GetEntitySetName<MembersController>());
return builder.GetEdmModel();
}

Action & Function

参考Microsoft Docs

动作和函数之间的区别在于: Action可以有副作用(side effect)而Function没有,Action应用场景或应用如复杂交易、一次操作多个实体、仅更新实体部分属性、发送非实体数据等

Action:

1
2
3
4
5
6
7
8
ODataModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Product>("Products");

// New code:
builder.Namespace = "ProductService";
builder.EntityType<Product>()
.Action("Rate")
.Parameter<int>("Rating");

使能名为‘Rate’的Action的接口 http://localhost/Products(1)/ProductService.Rate
controller中定义Action,方法名与操作名称相同
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
[HttpPost]
public async Task<IHttpActionResult> Rate([FromODataUri] int key, ODataActionParameters parameters)
{
if (!ModelState.IsValid)
{
return BadRequest();
}

int rating = (int)parameters["Rating"];
db.Ratings.Add(new ProductRating
{
ProductID = key,
Rating = rating
});

try
{
await db.SaveChangesAsync();
}
catch (DbUpdateException e)
{
if (!ProductExists(key))
{
return NotFound();
}
else
{
throw;
}
}

return StatusCode(HttpStatusCode.NoContent);
}

Function:
1
2
3
builder.EntityType<Product>().Collection
.Function("ProdFunc1")
.Returns<double>();

GET http://localhost:38479/Products/ProductService.ProdFunc1
controller中用同样的名称定义API方法
1
2
3
4
5
6
[HttpGet]
public IHttpActionResult MostExpensive()
{
var product = db.Products.Max(x => x.Price);
return Ok(product);
}

Unbound Function:
Action或Function应用于单个实体或集合,称之为绑定(binding),未绑定(unbound)的Action/Function称为服务的静态操作
1
2
3
builder.Function("GetSalesTaxRate")
.Returns<double>()
.Parameter<int>("PostalCode");

1
2
3
4
5
6
7
[HttpGet]
[ODataRoute("GetSalesTaxRate(PostalCode={postalCode})")]
public IHttpActionResult GetSalesTaxRate([FromODataUri] int postalCode)
{
double rate = 5.6; // Use a fake number for the sample.
return Ok(rate);
}

containment

1
2
3
4
5
6
7
8
[EnableQuery]         
[ODataRoute("Accounts({accountId})/PayinPIs({paymentInstrumentId})")]
public IHttpActionResult GetSinglePayinPI(int accountId, int paymentInstrumentId)
{
var payinPIs = _accounts.Single(a => a.AccountID == accountId).PayinPIs;
var payinPI = payinPIs.Single(pi => pi.PaymentInstrumentID == paymentInstrumentId);
return Ok(payinPI);
}

零和博弈,非零和博弈

非零和博弈表示在不同策略组合下各博弈方的得益之和是不确定的变量,故又称之为变和博弈。 如囚徒困境,中美关系,恋爱

零和博弈表示所有博弈方的利益之和为零或一个常数,即一方有所得,其他方必有所失。如绝大多数竞技,竞选,炒股

行业或社会环境成为存量市场,内卷是人们进行零和博弈的结果。

摘自百度百科《非零和博弈》:
烈日炎炎的一个下午,约翰·纳什教授给二十几个学生上课,教室窗外的楼下有几个工人正施工,机器的响声成了刺耳的噪音,于是纳什走到窗前狠狠地把窗户关上。马上有同学提出意见:“教授,请别关窗子,实在太热了!”而纳什教授一脸严肃地回答说:“课堂的安静比你舒不舒服重要得多!”然后转过身一边嘴里叨叨着“给你们来上课,在我看来不但耽误了你们的时间,也耽误了我的宝贵时间……”,一边在黑板上写着数学公式。
正当教授一边自语一边在黑板上写公式之际,一位叫阿丽莎的漂亮女同学(这位女同学后来成了纳什的妻子)走到窗边打开了窗子,电影中纳什用责备的眼神看着阿丽莎:“小姐……”而阿丽莎对窗外的工人说道:“打扰一下,嗨!我们有点小小的问题,关上窗户,这里会很热;开着,却又太吵。我想能不能请你们先修别的地方,大约45分钟就好了。”正在干活的工人愉快地说:“没问题!”又回头对自己的伙伴们说:“伙计们,让我们先休息一下吧!”阿丽莎回过头来快活地看着纳什教授,纳什教授也微笑地看着阿丽莎,既像是讲课,又像是在评论她的做法似地对同学们说:“你们会发现在多变性的微积分中,往往一个难题会有多种解答。”
而阿丽莎对“开窗难题”的解答,使得原本的一个零和博弈变成了另外一种结果:同学们既不必忍受室内的高温,教授也可以在安静的环境中讲课,结果不再是0,而成了+2。由此我们可以看到,很多看似无法调和的矛盾,其实并不一定是你死我活的僵局,那些看似零和博弈或者是负和博弈的问题,也会因为参与者的巧妙设计而转为正和博弈。

内卷和博弈

内卷是零和博弈的状态吗?
参与者是直接地进行利益竞争吗,似乎并不是哦,内卷的起因是掌握话语权的一方——在社会生产的范畴上是资本家,在就学教育上是招生办——限制了规则,在既定的有限条件下,低阶层的参与者为了自己的收益做出与之不匹配的付出。博弈是影响决策的活动,当然就要有决策的权力和空间。