标准数字格式字符串用于格式化通用数值类型。 标准数字格式字符串采用 Axx 的形式。

A 是称为“格式说明符” 的单个字母字符。

xx 是称为“精度说明符” 的可选整数。 精度说明符的范围从 0 到 99,并且影响结果中的位数。

常用格式

十进制(“D”)格式说明符

“D”或“d” :表示10进制;

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
int value;

value = 12345;
Console.WriteLine(value.ToString("D"));
// Displays 12345
Console.WriteLine(value.ToString("D8"));
// Displays 00012345

value = -12345;
Console.WriteLine(value.ToString("D"));
// Displays -12345
Console.WriteLine(value.ToString("D8"));
// Displays -00012345

定点(“F”)格式说明符

定点(“F”)格式说明符将数字转换为“-ddd.ddd…”形式的字符串,其中每个“d”表示一个数字 (0-9)。 如果该数字为负,则该字符串以减号开头。

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
int integerNumber;
integerNumber = 17843;
Console.WriteLine(integerNumber.ToString("F",
CultureInfo.InvariantCulture));
// Displays 17843.00

integerNumber = -29541;
Console.WriteLine(integerNumber.ToString("F3",
CultureInfo.InvariantCulture));
// Displays -29541.000

double doubleNumber;
doubleNumber = 18934.1879;
Console.WriteLine(doubleNumber.ToString("F", CultureInfo.InvariantCulture));
// Displays 18934.19

Console.WriteLine(doubleNumber.ToString("F0", CultureInfo.InvariantCulture));
// Displays 18934

doubleNumber = -1898300.1987;
Console.WriteLine(doubleNumber.ToString("F1", CultureInfo.InvariantCulture));
// Displays -1898300.2

Console.WriteLine(doubleNumber.ToString("F3",
CultureInfo.CreateSpecificCulture("es-ES")));
// Displays -1898300,199

百分比(“P”)格式说明符

百分比(“P”)格式说明符将数字乘以 100 并将其转换为表示百分比的字符串。

“P”或“p”

1
2
3
4
5
6
7
8
double number = .2468013;
Console.WriteLine(number.ToString("P", CultureInfo.InvariantCulture));
// Displays 24.68 %
Console.WriteLine(number.ToString("P",
CultureInfo.CreateSpecificCulture("hr-HR")));
// Displays 24,68%
Console.WriteLine(number.ToString("P1", CultureInfo.InvariantCulture));
// Displays 24.7 %

数字(“N”)格式说明符

数字(”N”)格式说明符将数字转换为”-d,ddd,ddd.ddd…”形式的字符串

“N”或“n”

1
2
3
4
5
6
7
8
9
10
double dblValue = -12445.6789;
Console.WriteLine(dblValue.ToString("N", CultureInfo.InvariantCulture));
// Displays -12,445.68
Console.WriteLine(dblValue.ToString("N1",
CultureInfo.CreateSpecificCulture("sv-SE")));
// Displays -12 445,7

int intValue = 123456789;
Console.WriteLine(intValue.ToString("N1", CultureInfo.InvariantCulture));
// Displays 123,456,789.0

十六进制(“X”)格式说明符

十六进制(“X”)格式说明符将数字转换为十六进制数的字符串。

“X”或“x”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int value; 

value = 0x2045e;
Console.WriteLine(value.ToString("x"));
// Displays 2045e
Console.WriteLine(value.ToString("X"));
// Displays 2045E
Console.WriteLine(value.ToString("X8"));
// Displays 0002045E

value = 123456789;
Console.WriteLine(value.ToString("X"));
// Displays 75BCD15
Console.WriteLine(value.ToString("X2"));
// Displays 75BCD15

十六进制的表示:
C语言、Shell、Python语言及其他相近的语言使用字首“0x”,例如“0x5A3”。开头的“0”令解析器更易辨认数,而“x”则代表十六进制。在“0x”中的“x”可以大写或小写。

参考:

.NET 中的格式类型

标准数字格式字符串

复合格式设置

自定义数字格式字符串

ABP框架学习记录(26)- Authorization 解析

本文 以 Volo.Abp 解决方案展开研究;

Volo.Abp 解决方案中,提供了单独的项目 实现 Authorization 功能,如下图:

QQ截图20191017152520.png

Authorization:中文解释,授权。ABP 中检查是否对 用户或者角色 授予权限。整个功能可以分为两个方面理解:
1,许可定义;
2,检查许可;

Permission

Permission 的中文解释为:许可,允许。

首先介绍 Permission 相关的类定义:

PermissionDefinition:许可定义,包括父级权限和子级列表。

QQ截图20191021114332.png

PermissionGroupDefinition:定义许可组;

QQ截图20191021131218.png

PermissionGrantResult:授权结果;

QQ截图20191021131648.png

IPermissionChecker:检查授权,提供 IsGrantedAsync 方法检查是否有权限。

QQ截图20191021104232.png

PermissionChecker:实现 IPermissionChecker 接口;

QQ截图20191021131755.png

IPermissionDefinitionContext:许可定义上下文,封装了 PermissionGroupDefinition 对象,并且提供了添加,移除的方法;

QQ截图20191021132317.png

PermissionDefinitionContext:实现 IPermissionDefinitionContext 接口;

QQ截图20191021132414.png

IPermissionDefinitionManager:管理 PermissionDefinition;

QQ截图20191021135949.png

PermissionDefinitionManager:默认实现 IPermissionDefinitionManager 接口。

QQ截图20191021140150.png

IPermissionDefinitionProvider:定义 PermissionDefinition 提供者接口;对 Define 方法传入的 IPermissionDefinitionContext 对象进行操作;

QQ截图20191021140648.png

PermissionDefinitionProvider:实现 IPermissionDefinitionProvider 接口;

QQ截图20191021140742.png

IPermissionStore:定义许可 存储接口;

QQ截图20191021141034.png

NullPermissionStoreIPermissionStore 接口的空实现。

IPermissionValueProvider:定义获取 PermissionValue 的接口;

QQ截图20191021151209.png

PermissionValueProvider:实现 IPermissionValueProvider 接口;

QQ截图20191021171013.png

RolePermissionValueProvider:提供角色相关许可;

UserPermissionValueProvider:提供用户相关许可;

ClientPermissionValueProvider:提供客户端相关许可;

IPermissionValueProviderManager:定义管理 IPermissionValueProvider 的接口;

QQ截图20191021171140.png

PermissionValueProviderManager:实现 IPermissionValueProviderManager 接口;

QQ截图20191021171437.png

PermissionOptions:许可选项;

QQ截图20191021141758.png

PermissionValueCheckContext:检查 PermissionValue 的上下文;

QQ截图20191021171820.png

AbpAuthorizationModule

这一部分主要分析 AbpAuthorizationModule 的实现。

Volo.Abp 解决方案中,其 Module 系统集成 Core 自带的 IServiceCollection ,通过自定义的 ServiceConfigurationContext 类,封装了 IServiceCollection 对象:

QQ截图20191021175125.png

AbpAuthorizationModule:定义ModuleOnRegistred 扩展方法 添加 拦截器注册者;

QQ截图20191021174716.png

PreConfigureServices

AuthorizationInterceptorRegistrar:拦截器注册类;

QQ截图20191021175737.png

AuthorizationInterceptor:定义拦截器;

QQ截图20191021175839.png

MethodInvocationAuthorizationContext:定义方法调用 许可上下文,校验方法是否被允许;

QQ截图20191021180118.png

IMethodInvocationAuthorizationService:定义校验方法许可的接口;

MethodInvocationAuthorizationService:实现 IMethodInvocationAuthorizationService 接口;

QQ截图20191021180541.png

ConfigureServices

QQ截图20191021180851.png

PermissionRequirement:许可参数;

QQ截图20191021181401.png

PermissionRequirementHandler:定义 回调 Handler

QQ截图20191021181600.png

其它

IAbpAuthorizationService:定义 Abp 授权服务接口;继承 Dot Net Core自带 IAuthorizationService 接口;

AbpAuthorizationService:实现 IAuthorizationService 接口;

QQ截图20191021182258.png

IAbpAuthorizationPolicyProvider:定义 授权 策略 提供者接口;继承 Dot Net Core 自带 IAuthorizationPolicyProvider 接口;

AbpAuthorizationPolicyProvider:实现 IAbpAuthorizationPolicyProvider 接口;

QQ截图20191021182527.png

Identity笔记之 - Claim

本文介绍 Claim 相关概念,具体类在 System.Security.Claims 命名空间下。

Claim

Claim:要求;主张;声称; 宣称; 自称; 认领

Identity 可以理解成 证件单元,以键值对表示:

例如:姓名:特斯拉

QQ截图20191017150008.png

ClaimsIdentity

QQ截图20191017150602.png

Identity 可以理解成 身份证件,即包括一系列 Claim 的集合;

ClaimsPrincipal

Principal:主体,委托人

ClaimsPrincipal 可以理解成 证件当事人;

QQ截图20191017151234.png

参考:

ASP.NET Core 之 Identity 入门(一)

CSharp-预处理器指令

预处理器指令

预处理器指令(preprocessor directive):用于在 C# 源代码中嵌入的编译器命令,告诉C#编译器要编译哪些代码,并指出如何处理特定的错误和警告。C#预处理器指令还可以告诉C#编辑器有关代码组织的信息。

选中 项目 ,右键 属性,在 生成 标签:

QQ截图20191014141323.png

1
2
3
4
5
6
7
#if DEBUG
Console.WriteLine("DEBUG");
#endif

#if TRACE
Console.WriteLine("TRACE");
#endif

具体预处理器指令参考:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#if
#else
#elif
#endif
#define
#undef
#warning
#error
#line
#region
#endregion
#pragma
#pragma warning
#pragma checksum

ConditionalAttribute

指示编译器,除非定义了指定的有条件编译符号,否则,应忽略方法调用或属性。

注意:

#if DEBUG:此处的代码在发布时甚至不会到达IL。

[Conditional(“DEBUG”)]:这个代码将到达IL,但是当设置为DEBUG时,才会被调用。

1
2
3
4
5
6
7
8
[Conditional("DEBUG")]
public void DoSomething() { }

public void Foo()
{
DoSomething(); //Code compiles and is cleaner, DoSomething always
//exists, however this is only called during DEBUG.
}

参考:

C# 预处理器指令

如何:使用跟踪和调试执行有条件编译

如何:创建、初始化和配置跟踪开关

-define(C# 编译器选项)

管理项目和解决方案属性

#if DEBUG vs. Conditional(“DEBUG”)

AsyncLocal

表示对于给定异步控制流(如异步方法)是本地数据的环境数据。

注意:await外的AsyncLocal值可以传递到await内, await内的AsyncLocal值无法传递到await外(只能读取不能修改)。

例子:

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
namespace ConsoleApp7
{
class Program
{
static void Main(string[] args)
{
var task = Task.Run(async () =>
{
v.Value = 123;
await Intercept.Invoke(); // value = 888

// 恢复备份的上下文
Console.WriteLine(Program.v.Value); // 输出 123
});
task.Wait();

Console.ReadKey();
}

public static AsyncLocal<int> v = new AsyncLocal<int>();
}

public class Intercept
{
public static async Task Invoke()
{
await Task.FromResult(0);

Program.v.Value = 888;
}
}
}

msdn例子:

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
using System;
using System.Threading;
using System.Threading.Tasks;

class Example
{
static AsyncLocal<string> _asyncLocalString = new AsyncLocal<string>();

static ThreadLocal<string> _threadLocalString = new ThreadLocal<string>();

static async Task AsyncMethodA()
{
// Start multiple async method calls, with different AsyncLocal values.
// We also set ThreadLocal values, to demonstrate how the two mechanisms differ.
_asyncLocalString.Value = "Value 1";
_threadLocalString.Value = "Value 1";
var t1 = AsyncMethodB("Value 1");

_asyncLocalString.Value = "Value 2";
_threadLocalString.Value = "Value 2";
var t2 = AsyncMethodB("Value 2");

// Await both calls
await t1;
await t2;
}

static async Task AsyncMethodB(string expectedValue)
{
Console.WriteLine("Entering AsyncMethodB.");
Console.WriteLine(" Expected '{0}', AsyncLocal value is '{1}', ThreadLocal value is '{2}'",
expectedValue, _asyncLocalString.Value, _threadLocalString.Value);
await Task.Delay(100);
Console.WriteLine("Exiting AsyncMethodB.");
Console.WriteLine(" Expected '{0}', got '{1}', ThreadLocal value is '{2}'",
expectedValue, _asyncLocalString.Value, _threadLocalString.Value);
}

static async Task Main(string[] args)
{
await AsyncMethodA();
}
}
// The example displays the following output:
// Entering AsyncMethodB.
// Expected 'Value 1', AsyncLocal value is 'Value 1', ThreadLocal value is 'Value 1'
// Entering AsyncMethodB.
// Expected 'Value 2', AsyncLocal value is 'Value 2', ThreadLocal value is 'Value 2'
// Exiting AsyncMethodB.
// Expected 'Value 2', got 'Value 2', ThreadLocal value is ''
// Exiting AsyncMethodB.
// Expected 'Value 1', got 'Value 1', ThreadLocal value is ''

ThreadLocal

提供数据的线程本地存储。

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
using System;
using System.Threading;
using System.Threading.Tasks;

class ThreadLocalDemo
{
// Demonstrates:
// ThreadLocal(T) constructor
// ThreadLocal(T).Value
// One usage of ThreadLocal(T)
static void Main()
{
// Thread-Local variable that yields a name for a thread
ThreadLocal<string> ThreadName = new ThreadLocal<string>(() =>
{
return "Thread" + Thread.CurrentThread.ManagedThreadId;
});

// Action that prints out ThreadName for the current thread
Action action = () =>
{
// If ThreadName.IsValueCreated is true, it means that we are not the
// first action to run on this thread.
bool repeat = ThreadName.IsValueCreated;

Console.WriteLine("ThreadName = {0} {1}", ThreadName.Value, repeat ? "(repeat)" : "");
};

// Launch eight of them. On 4 cores or less, you should see some repeat ThreadNames
Parallel.Invoke(action, action, action, action, action, action, action, action);

// Dispose when you are done
ThreadName.Dispose();
}
}
// This multithreading example can produce different outputs for each 'action' invocation and will vary with each run.
// Therefore, the example output will resemble but may not exactly match the following output (from a 4 core processor):
// ThreadName = Thread5
// ThreadName = Thread6
// ThreadName = Thread4
// ThreadName = Thread6 (repeat)
// ThreadName = Thread1
// ThreadName = Thread4 (repeat)
// ThreadName = Thread7
// ThreadName = Thread5 (repeat)

参考:

AsyncLocal

ThreadLocal

AsyncLocal的运作机制和陷阱

How to make AsyncLocal flow to siblings?

ABP框架学习记录(25)- MultiTenancy 解析

简介

多租户和单租户托管是SaaS公司提供服务的两种方式。多租户托管是指在同一软件实例上存在许多客户端,它们共享基础结构,数据库和/或应用程序服务器。它便宜一些,但是有风险。单租户是指租户不共享任何东西。它更昂贵并且需要更多的管理,因为它需要为每个客户端运行完整的软件堆栈。

软件即服务(SaaS)产品可以具有各种级别的多租户。在应用程序服务器级别,可以有一个具有负载均衡功能的应用程序服务器池,可以为多个客户端提供服务。在数据库级别,每个租户可以有一个数据库,也可以在所有租户之间共享一个数据库。

详解

MultiTenancy:多租户

MultiTenancy 在ABP项目的目录位置:

QQ截图20190925151033.png

MultiTenancySides:标识是租户,租主;

QQ截图20190925153759.png

MultiTenancySideAttribute:声明多租户 特性;

QQ截图20190925162718.png

TenantInfo:租户信息,只有Id,和租户名称;

QQ截图20190925155312.png

ITenantStore:查找租户信息;

NullTenantStore:默认实现 ITenantStore 接口,

AbpKernelModule 注册,如果没有注册过,则注册 NullTenantStore

QQ截图20190925160412.png

Abp.Zero.Common 项目中,TenantStore 实现 ITenantStore:

QQ截图20190925160718.png

TenantResolverCacheItem:租户获取缓存项

ITenantResolverCache:定义获取租户缓存接口;

NullTenantResolverCacheITenantResolverCache 接口的空实现;

Abp.AspNetCore.MultiTenancy 项目, 在 HttpContextTenantResolverCache 类实现 HttpContextTenantResolverCache 接口:

QQ截图20190925162409.png

ITenantResolveContributor:定义租户解析参与者接口;即实际解析租户的实现类

例如,在 Abp.AspNetCore.MultiTenancy 项目的 DomainTenantResolveContributor 类:

QQ截图20190925163152.png

ITenantResolver/TenantResolver:租户解析者,相当于 Manager 的角色;

匹配到真正的 ITenantResolveContributor 接口实现:

QQ截图20190925163736.png

QQ截图20190925163623.png

Abp.Zero.Common

AbpTenantManager:管理 租户信息,包括持久化,Feature等;

QQ截图20190925165017.png

QQ截图20190925170710.png

QQ截图20190925170749.png

参考:

Single-tenant vs multi-tenant hosting

ABP框架学习记录(24)- Notifications 解析

Notifications 封装了通知的功能,实现 Notification 的定义,存储,发送,订阅等功能,支持租户通知,用户通知。通过 IRealTimeNotifier 接口,实现统一的 Notification 功能。支持 SignalR

Notifications 在ABP项目中的位置:
QQ截图20190923141119.png

表-实体类对应

首先,介绍关于 Notifications 的表-实体类;

NotificationInfo:用于存储需要发送的通知;

QQ截图20190923181330.png

NotificationSubscriptionInfo: 用于存储 通知订阅。

QQ截图20190923181457.png

TenantNotificationInfo: 分配给其相关租户的通知。

QQ截图20190924092140.png

UserNotificationInfo:用户存储用户通知

QQ截图20190924092240.png

通知数据

NotificationData:用户 通知数据的 存储;

QQ截图20190924134512.png

MessageNotificationData:继承 NotificationData, 可以用户简单的通知消息;

QQ截图20190924134619.png

LocalizableMessageNotificationData:继承 NotificationData, 可以用户表示本地的通知消息数据;

QQ截图20190924134652.png

通知定义管理

NotificationDefinition:定义通知

QQ截图20190924143636.png

NotificationDefinitionManager:实现 INotificationDefinitionManager 接口,用于管理 NotificationDefinition;

QQ截图20190924143805.png

AbpKernelModulePostInitialize 方法中初始化;

QQ截图20190924144304.png

NotificationDefinitionContext:定义 NotificationDefinition 上下文;

QQ截图20190924143854.png

NotificationDefinitionManagerExtensions:扩展方法,获取给定用户的所有通知;

QQ截图20190924144047.png

配置

INotificationConfiguration:配置通知;

NotificationConfiguration:实现 INotificationConfiguration 接口;

QQ截图20190924150138.png

AbpCoreInstaller 中注册:

QQ截图20190924150305.png

持久化

INotificationStore:定义通知持久化接口;

QQ截图20190924151312.png

Abp.Zero.Common 项目中,使用仓储模式,提供 INotificationStore 接口的实现 NotificationStore

QQ截图20190924151629.png

UserNotificationState:定义通知的状态,已读,未读;

管理用户通知

IUserNotificationManager:定义用户通知管理接口;

UserNotificationManager:实现 IUserNotificationManager 接口;

QQ截图20190924165743.png

UserNotification:表示发送给用户的通知;

QQ截图20190924165934.png

TenantNotification:代表租户/用户 已经发送的通知;

QQ截图20190924170219.png

发布通知

INotificationPublisher:定义发布通知的接口;

NotificationPublisher:实现 INotificationPublisher 接口;

QQ截图20190924170626.png

PublishAsync 异步方法:

QQ截图20190924170842.png

NotificationDistributionJob:后台工作,去通知用户;

QQ截图20190924172001.png

订阅通知

NotificationSubscription:表示一个用户订阅的通知;

INotificationSubscriptionManager:管理订阅通知;

NotificationSubscriptionManager:实现 INotificationSubscriptionManager

QQ截图20190924171659.png

通知设置

NotificationSettingNames:定义通知开关 配置名;

QQ截图20190924172434.png

NotificationSettingProvider:适配器

QQ截图20190924172654.png

AbpKernelModule 模块初始化:

QQ截图20190924172751.png

发送通知

INotificationDistributer:定义发送给用户通知的接口;

DefaultNotificationDistributer:实现 INotificationDistributer 接口;

QQ截图20190924173343.png

QQ截图20190924173922.png

QQ截图20190924174210.png

IRealTimeNotifier:定义向用户发送实时通知的接口;

ABP框架学习记录(23)- SignalR解析

SignalR是一个.NET Core/.NET Framework的开源实时框架. SignalR的可使用Web Socket, Server Sent Events 和 Long Polling作为底层传输方式.

SignalR基于这三种技术构建, 抽象于它们之上, 它让你更好的关注业务问题而不是底层传输技术问题.

SignalR这个框架分服务器端和客户端, 服务器端支持ASP.NET Core 和 ASP.NET; 而客户端除了支持浏览器里的javascript以外, 也支持其它类型的客户端, 例如桌面应用.

整体目录:

QQ截图20190921105001.png

AbpHubBase: 继承 Hub 抽象类,定义成 Abp SignalR 的抽象类,类成员包括 AbpSession,IocResolver,LocalizationManager 等;

QQ截图20190921110241.png

AbpHubBaseOfClientType:定义泛型 Hub;

QQ截图20190921110421.png

OnlineClientHubBase:在线客户端 Hub 基类;

QQ截图20190921110621.png

QQ截图20190921110755.png

AbpCommonHub:公用 Hub,提供注册方法;

QQ截图20190921111019.png

SignalRRealTimeNotifier: 实现 IRealTimeNotifier 接口;

QQ截图20190921114233.png

QQ截图20190921134226.png

AbpAspNetCoreSignalRModule:定义 SignalRModule;

QQ截图20190921134356.png

参考:

ASP.NET Core的实时库: SignalR – 预备知识

ASP.NET Core的实时库: SignalR简介及使用

ABP框架学习记录(22)-RealTime的实现

RealTime,翻译为 即时的,实时的;

RealTime 的实现,在ABP项目中的位置:

QQ截图20190912094652.png

IOnlineClientStore:提供存储接口;

QQ截图20190920151501.png

InMemoryOnlineClientStore:内存存储的实现;

QQ截图20190920151559.png

IOnlineClient:表示一个连接到应用的客户端;

QQ截图20190920151741.png

OnlineClientIOnlineClient 接口的实现;

QQ截图20190920152302.png

OnlineClientEventArgs:客户端事件

QQ截图20190920152812.png

OnlineUserEventArgs:用户事件

QQ截图20190920153024.png

IOnlineClientManager:管理连接到应用的客户端;

QQ截图20190920155746.png

OnlineClientManagerIOnlineClientManager 的实现;

QQ截图20190920160558.png

QQ截图20190920160908.png

ABP框架学习记录(21)- Repositories的实现

Repositories 实现在ABP项目的目录位置:

QQ截图20190911102434.png

IRepository:定义存储接口,所有的存储库必须实现,即按照约定标识;存储库需要实现泛型版本而不是此接口;

QQ截图20190911110920.png

IRepositoryOfTEntityAndTPrimaryKey:泛型存储接口

QQ截图20190911111150.png

QQ截图20190911132904.png

AbpRepositoryBase:继承 IRepository{TEntity,TPrimaryKey} 泛型接口的泛型抽象类;

QQ截图20190911133034.png

QQ截图20190911142736.png

AutoRepositoryTypesAttribute:自动为实体生成 Repository

QQ截图20190911151234.png

QQ截图20190911151333.png

AbpEntityFrameworkModule : 解析出 IEfGenericRepositoryRegistrar,并应用 RegisterForDbContext 方法;

QQ截图20190911151456.png

EfGenericRepositoryRegistrar:注册 Repository;

QQ截图20190911151633.png

ISupportsExplicitLoading:支持需要明确加载的对象;

QQ截图20190911142957.png

QQ截图20190911105512.png

QQ截图20190911110237.png

RepositoryExtensionsRepository 扩展方法;

QQ截图20190911154020.png

0%