Flutter源码阅读分析:引擎初始化与启动_董小虫的博客-CSDN博客_flutter引擎初始化

mikel阅读(619)

来源: Flutter源码阅读分析:引擎初始化与启动_董小虫的博客-CSDN博客_flutter引擎初始化

引擎初始化与启动
本文也发布于本人的知乎专栏:https://zhuanlan.zhihu.com/p/394560540

0. 前言
Flutter是当前比较火热的前端开发框架,正好我最近也在做和Flutter引擎相关的工作,就顺手研究一下Flutter的源码。
源码主要分为两部分:

Engine,是可供Flutter宿主应用提供的一个可移植运行时。Engine实现了Flutter的核心库,包括动画、图形、文件、网络I/O、访问支持、插件架构和Dart的运行时、编译工具链;
Flutter Framework,大部分的Flutter开发者主要通过Flutter Framework交互。Framework提供了一个现代的、可交互的框架,以及一个丰富的平台、布局、基础部件的集合。
Flutter官方源码下载路径:
engine: https://github.com/flutter/engine
flutter framework: https://github.com/flutter/flutter

本文主要分析引擎的初始化流程和启动流程。

1. 初始化流程
1.1 Android平台适配层
众所周知,Android应用的入口基本都是Activity,那么我们就先从Flutter的Activity开始着手分析。

1.1.1 FlutterActivity
在Engine中存在两个FlutterActivity(shell/platform/Android/io/flutter/app/FlutterActivity.java和shell/platform/android/io/flutter/embedding/android/FlutterActivity.java),其中在2020年5月13日的代码提交中,前一个FlutterActivity注释修改为废弃Activity基类。所以现在可用的Activity基类是后一个。
根据FlutterActivity的注释,我们可知:
FlutterActivity是将Flutter集成到Android应用中的最简单最直接的方式,用于显示一个全屏的Flutter UI。主要职责是:

显示一个Android的lauch screen;
显示Flutter的splash screen;
设置状态栏;
选择Dart执行应用包路径和入口点;
选择Flutter的初始化路由;
如果需要的话,渲染透明度;
提供子类钩子,提供和配置FlutterEngine。
先从FlutterActivity定义看起:

public class FlutterActivity extends Activity
implements FlutterActivityAndFragmentDelegate.Host, LifecycleOwner
1
2
FlutterActivity继承于Activity。FlutterActivityAndFragmentDelegate.Host接口是在FlutterActivityAndFragmentDelegate中FlutterActivity持有类。

Activity的onCreate方法是入口关键方法,看一下FlutterActivity的onCreate方法:

// ./shell/platform/android/io/flutter/embedding/android/FlutterActivity.java
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
switchLaunchThemeForNormalTheme(); // 设置主题

super.onCreate(savedInstanceState);

lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_CREATE); // 设置Android生命周期

delegate = new FlutterActivityAndFragmentDelegate(this); // [1]
delegate.onAttach(this);
delegate.onActivityCreated(savedInstanceState);

configureWindowForTransparency(); // 设置背景透明模式
setContentView(createFlutterView()); // [2]
configureStatusBarForFullscreenFlutterExperience(); // 配置系统状态栏
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[1] FlutterActivityAndFragmentDelegate类实现了FlutterActivity与FlutterFragment之间相同的逻辑。
[2] 通过调用FlutterActivityAndFragmentDelegate的onCreateView方法创建出FlutterView并将其添加到界面上。FlutterView用于在Android设备上显示Flutter UI。
1.1.2 FlutterActivityAndFragmentDelegate
下面就再分析一下FlutterActivityAndFragmentDelegate的源码逻辑:

// ./shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java
final class FlutterActivityAndFragmentDelegate {

FlutterActivityAndFragmentDelegate(@NonNull Host host) {
this.host = host; // 保存FlutterActivity对象
}

// [3]
void onAttach(@NonNull Context context) {

setupFlutterEngine(); // 创建FlutterEngine

}

// [4]
@NonNull
View onCreateView(
LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {

// 创建FlutterSurfaceView,用于提供绘制Flutter UI的Surface
FlutterSurfaceView flutterSurfaceView =
new FlutterSurfaceView(
host.getActivity(), host.getTransparencyMode() == TransparencyMode.transparent);

flutterView = new FlutterView(host.getActivity(), flutterSurfaceView);

flutterSplashView = new FlutterSplashView(host.getContext());

flutterSplashView.displayFlutterViewWithSplash(flutterView, host.provideSplashScreen());
flutterView.attachToFlutterEngine(flutterEngine);
return
}

return flutterSplashView;
}
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
[3] onAttach方法主要做了以下事情:1. 初始化Flutter系统;2. 获取或者创建一个FlutterEngine;3. 创建和配置PlatformPlugin;4. 将FlutterEngine附在Activity上;5. 通过Host的configureFlutterEngine方法配置FlutterEngine。
[4] ‘onCreateView’方法主要做以下事情:1. 在View树中创建一个新的FlutterView;2. 在FlutterView中添加一个FlutterUiDisplayListener;3. 将FlutterEngine附着到FlutterView上;4. 返回这个新的View树。
在这里出现了两个比较重要的类,FlutterView和FlutterEngine。

1.1.3 FlutterView
FlutterView的作用是将Flutter UI通过对应的FlutterEngine绘制后,显示在Android设备上。这里有两种渲染模式:surface和texture。
在一般情况,我们都使用surface模式,即将Flutter UI绘制在SurfaceView上,这种模式拥有最优的性能,缺点是无法置于两个其他Android View的z-index之间,同时也无法动画化或者进行变换。
下面我们看一下FlutterView的源码实现:

// ./shell/platform/android/io/flutter/embedding/android/FlutterView.java
public class FlutterView extends FrameLayout {

private FlutterView(
@NonNull Context context,
@Nullable AttributeSet attrs,
@NonNull FlutterSurfaceView flutterSurfaceView) {
super(context, attrs);

this.flutterSurfaceView = flutterSurfaceView;
this.renderSurface = flutterSurfaceView;

init();
}

private void init() {

addView(flutterSurfaceView);

// FlutterView needs to be focusable so that the InputMethodManager can interact with it.
setFocusable(true);
setFocusableInTouchMode(true);
}

// 将当前FlutterView连接到给定的FLutterEngine
// FlutterView将通过给定的FlutterEngine绘制UI,同时也将开始将后续的交互时间传递到FlutterEngine,
// 例如触摸事件、键盘事件等。
public void attachToFlutterEngine(@NonNull FlutterEngine flutterEngine) {

this.flutterEngine = flutterEngine;
// Instruct our FlutterRenderer that we are now its designated RenderSurface.
FlutterRenderer flutterRenderer = this.flutterEngine.getRenderer();

renderSurface.attachToRenderer(flutterRenderer); // [5]

// Initialize various components that know how to process Android View I/O
// in a way that Flutter understands.

// Push View and Context related information from Android to Flutter.
sendUserSettingsToFlutter();
sendLocalesToFlutter(getResources().getConfiguration());
sendViewportMetricsToFlutter();

flutterEngine.getPlatformViewsController().attachToView(this); // PlatformViewsController获取当前FlutterView

}
}
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
[5] 将FlutterSurfaceView的Surface提供给指定的FlutterRender,用于将Flutter UI绘制到当前的FlutterSurfaceView。
1.1.4 FlutterEngine
FlutterEngine是一个独立的Flutter运行环境,是Dart代码运行在Android应用的容器。

// ./shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java
/** Fully configurable {@code FlutterEngine} constructor. */
public FlutterEngine(
@NonNull Context context,
@NonNull FlutterLoader flutterLoader,
@NonNull FlutterJNI flutterJNI,
@NonNull PlatformViewsController platformViewsController,
@Nullable String[] dartVmArgs,
boolean automaticallyRegisterPlugins) {
this.flutterJNI = flutterJNI;
flutterLoader.startInitialization(context.getApplicationContext());

attachToJni();
this.dartExecutor = new DartExecutor(flutterJNI, context.getAssets());
this.dartExecutor.onAttachedToJNI();
this.renderer = new FlutterRenderer(flutterJNI);

xxxChannel = new XxxChannel(…); // 创建各个消息通道,用于传递事件、消息

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
DartExecutor用于配置、启动、执行Dart代码,在后续分析中再详细研究。
在FlutterActivityAndFragmentDelegate的setupFlutterEngine方法中调用的FlutterEngine构造方法没有传入FlutterJNI,在三参数构造方法中,会创建FlutterJNI对象,并传入全参数构造方法。FlutterJNI则是沟通Android的Java与Flutter引擎核心C++代码的桥梁。

1.1.5 FlutterJNI
FlutterJNI是Flutter内嵌Java代码和引擎C++代码之间的接口。
Flutter引擎的代码是使用C++实现的。Android Flutter嵌入则负责协调Android系统事件和应用用户交互事件。这些事件协调需要消息交流接口,这就需要用到JNI(Java Native Interface)来穿过Java/native边界。
在Flutter的设计中,所有的JNI接口都集中在FlutterJNI这个类中。这么做主要有以下几个原因:

JNI调用都是静态的,且没有Java实现,因此没有理由将调用与不同的类相关联;
所有的JNI调用必须在C/C++代码中注册,当增加额外的包含JNI调用时,这个注册会变得非常复杂;
很多Android开发者对native开发或者JNI不熟悉,因此在后续维护中减少JNI的接口是很有必要的。
大部分FlutterJNI中的调用都与特定的“platform view”相关,而“platform view”的数量可能会很多。所以,在执行了attachToNative方法后,每个FlutterJNI实例都持有一个本地“platform view”的ID,且这个ID与bendingC/C++引擎代码共享。这个ID会被传递到所有的具体platform view的本地方法。

// ./shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java
public class FlutterJNI {

public FlutterJNI() {
// We cache the main looper so that we can ensure calls are made on the main thread
// without consistently paying the synchronization cost of getMainLooper().
mainLooper = Looper.getMainLooper();
}

@UiThread
public void attachToNative(boolean isBackgroundView) {
ensureRunningOnMainThread();
ensureNotAttachedToNative();
nativePlatformViewId = nativeAttach(this, isBackgroundView);
}

private native long nativeAttach(@NonNull FlutterJNI flutterJNI, boolean isBackgroundView);

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// ./shell/platform/android/platform_view_android_jni.cc
bool RegisterApi(JNIEnv* env) {
static const JNINativeMethod flutter_jni_methods[] = {
{
.name = “nativeAttach”,
.signature = “(Lio/flutter/embedding/engine/FlutterJNI;Z)J”,
.fnPtr = reinterpret_cast<void*>(&AttachJNI),
},

};
if (env->RegisterNatives(g_flutter_jni_class->obj(), flutter_jni_methods,
fml::size(flutter_jni_methods)) != 0) {
FML_LOG(ERROR) << “Failed to RegisterNatives with FlutterJNI”;
return false;
}

}

static jlong AttachJNI(JNIEnv* env,
jclass clazz,
jobject flutterJNI,
jboolean is_background_view) {
fml::jni::JavaObjectWeakGlobalRef java_object(env, flutterJNI);
auto shell_holder = std::make_unique<AndroidShellHolder>( // [6]
FlutterMain::Get().GetSettings(), java_object, is_background_view);
if (shell_holder->IsValid()) {
return reinterpret_cast<jlong>(shell_holder.release());
} else {
return 0;
}
}
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
[6] AndroidShellHolder是C/C++的Shell持有类。
1.1.6 AndroidShellHolder
在AndroidShellHolder中保存有Flutter设置参数、FlutterJNI的Java引用、PlatformViewAndroid对象(该对象在后续创建)、Shell对象等。

// ./shell/platform/android/android_shell_holder.cc
AndroidShellHolder::AndroidShellHolder(
flutter::Settings settings,
fml::jni::JavaObjectWeakGlobalRef java_object,
bool is_background_view)
: settings_(std::move(settings)), java_object_(java_object) {

// 创建三个线程:UI线程、GPU线程、IO线程
thread_host_ = {thread_label, ThreadHost::Type::UI | ThreadHost::Type::GPU |
ThreadHost::Type::IO};

fml::WeakPtr<PlatformViewAndroid> weak_platform_view;
Shell::CreateCallback<PlatformView> on_create_platform_view =
[is_background_view, java_object, &weak_platform_view](Shell& shell) {
std::unique_ptr<PlatformViewAndroid> platform_view_android;

platform_view_android = std::make_unique<PlatformViewAndroid>( // [7]
shell, // delegate
shell.GetTaskRunners(), // task runners
java_object, // java object handle for JNI interop
shell.GetSettings()
.enable_software_rendering // use software rendering
);
weak_platform_view = platform_view_android->GetWeakPtr();
return platform_view_android;
};

// [8]
shell_ =
Shell::Create(task_runners, // task runners
GetDefaultWindowData(), // window data
settings_, // settings
on_create_platform_view, // platform view create callback
on_create_rasterizer // rasterizer create callback
);
platform_view_ = weak_platform_view;

}
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
[7] 创建PlatformViewAndroid,该类继承于PlatformView。各平台会根据自身特点继承PlatformView类,实现自身平台的PlatformView。
[8] 这里创建出了一个Shell对象,该类是Flutter引擎的关键类,此后的代码将于平台无关,为各个平台的通用代码。
1.2. 通用Shell层
1.2.1 Shell
Shell类是Flutter引擎中最重要的类之一。当嵌入应用创建了一个Flutter应用,都将创建一个Shell的实例,且嵌入者只持有一个Shell的unique指针。
Shell是Flutter应用的“中枢神经系统”,包含了多个组件,并继承它们相应的Delegate类。
下面看一下创建Shell的代码实现:

// ./shell/common/shell.cc
std::unique_ptr<Shell> Shell::Create(
TaskRunners task_runners,
const WindowData window_data,
Settings settings,
Shell::CreateCallback<PlatformView> on_create_platform_view,
Shell::CreateCallback<Rasterizer> on_create_rasterizer) {

auto vm = DartVMRef::Create(settings); // 创建Dart虚拟机
auto vm_data = vm->GetVMData();
return Shell::Create(std::move(task_runners), //
std::move(window_data), //
std::move(settings), //
vm_data->GetIsolateSnapshot(), // isolate snapshot
on_create_platform_view, //
on_create_rasterizer, //
std::move(vm) //
);
}

std::unique_ptr<Shell> Shell::Create(
TaskRunners task_runners,
const WindowData window_data,
Settings settings,
fml::RefPtr<const DartSnapshot> isolate_snapshot,
const Shell::CreateCallback<PlatformView>& on_create_platform_view,
const Shell::CreateCallback<Rasterizer>& on_create_rasterizer,
DartVMRef vm) {

std::unique_ptr<Shell> shell;
fml::TaskRunner::RunNowOrPostTask(
task_runners.GetPlatformTaskRunner(),
fml::MakeCopyable([&latch, //
vm = std::move(vm), //
&shell, //
task_runners = std::move(task_runners), //
window_data, //
settings, //
isolate_snapshot = std::move(isolate_snapshot), //
on_create_platform_view, //
on_create_rasterizer //
]() mutable {
shell = CreateShellOnPlatformThread(std::move(vm),
std::move(task_runners), //
window_data, //
settings, //
std::move(isolate_snapshot), //
on_create_platform_view, //
on_create_rasterizer //
);

}));
return shell;
}

std::unique_ptr<Shell> Shell::CreateShellOnPlatformThread(
DartVMRef vm,
TaskRunners task_runners,
const WindowData window_data,
Settings settings,
fml::RefPtr<const DartSnapshot> isolate_snapshot,
const Shell::CreateCallback<PlatformView>& on_create_platform_view,
const Shell::CreateCallback<Rasterizer>& on_create_rasterizer) {

auto shell =
std::unique_ptr<Shell>(new Shell(std::move(vm), task_runners, settings));

// Create the rasterizer on the raster thread.
fml::TaskRunner::RunNowOrPostTask(
task_runners.GetRasterTaskRunner(), [&rasterizer_promise, //
&snapshot_delegate_promise,
on_create_rasterizer, //
shell = shell.get() //
]() {
std::unique_ptr<Rasterizer> rasterizer(on_create_rasterizer(*shell));

});

// Create the platform view on the platform thread (this thread).
auto platform_view = on_create_platform_view(*shell.get());

// Ask the platform view for the vsync waiter. This will be used by the engine
// to create the animator.
auto vsync_waiter = platform_view->CreateVSyncWaiter();

// Create the IO manager on the IO thread.

fml::TaskRunner::RunNowOrPostTask(
io_task_runner,
[&io_manager_promise, //
&weak_io_manager_promise, //
&unref_queue_promise, //
platform_view = platform_view->GetWeakPtr(), //
io_task_runner, //
is_backgrounded_sync_switch = shell->GetIsGpuDisabledSyncSwitch() //
]() {
auto io_manager = std::make_unique<ShellIOManager>(
platform_view.getUnsafe()->CreateResourceContext(),
is_backgrounded_sync_switch, io_task_runner);

});

// Create the engine on the UI thread.

fml::TaskRunner::RunNowOrPostTask(
shell->GetTaskRunners().GetUITaskRunner(),
fml::MakeCopyable([&engine_promise, //
shell = shell.get(), //
&dispatcher_maker, //
&window_data, //
isolate_snapshot = std::move(isolate_snapshot), //
vsync_waiter = std::move(vsync_waiter), //
&weak_io_manager_future, //
&snapshot_delegate_future, //
&unref_queue_future //
]() mutable {
const auto& task_runners = shell->GetTaskRunners();

// The animator is owned by the UI thread but it gets its vsync pulses
// from the platform.
auto animator = std::make_unique<Animator>(*shell, task_runners,
std::move(vsync_waiter));

engine_promise.set_value(std::make_unique<Engine>(
*shell, //
dispatcher_maker, //
*shell->GetDartVM(), //
std::move(isolate_snapshot), //
task_runners, //
window_data, //
shell->GetSettings(), //
std::move(animator), //
weak_io_manager_future.get(), //
unref_queue_future.get(), //
snapshot_delegate_future.get() //
));
}));
if (!shell->Setup(std::move(platform_view), //
engine_future.get(), //
rasterizer_future.get(), //
io_manager_future.get()) //
) {
return nullptr;
}
return shell;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
从代码中可以看出,在platform线程中创建了Shell,之后分别在栅格化线程中创建Rasterizer,在platform线程中创建PlatformView,在IO线程中创建ShellIOManager,在UI线程中创建Engine,并将这四者设置到Shell中去。
Shell分别继承了四者的Delegate,四者通过相应的Delegate将事件传递到Shell。
下面分别看一下这四个类。

1.2.2 PlatformView
在Android平台中,真正实现的是PlarformViewAndroid类,主要方法实现的功能都是在栅格化线程中对AndroidSurface进行操作。看一下NotifyCreated方法的实现:

// ./shell/platform/android/platform_view_android.cc
void PlatformViewAndroid::NotifyCreated(
fml::RefPtr<AndroidNativeWindow> native_window) {
if (android_surface_) {
InstallFirstFrameCallback();

fml::TaskRunner::RunNowOrPostTask(
task_runners_.GetRasterTaskRunner(),
[&latch, surface = android_surface_.get(),
native_window = std::move(native_window)]() {
surface->SetNativeWindow(native_window);

});

}

PlatformView::NotifyCreated(); // 此处调用了父类的对应方法
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// ./shell/common/platform_view.cc
void PlatformView::NotifyCreated() {
std::unique_ptr<Surface> surface;
auto* platform_view = this;

fml::TaskRunner::RunNowOrPostTask(
task_runners_.GetRasterTaskRunner(), [platform_view, &surface, &latch]() {
surface = platform_view->CreateRenderingSurface();

});

delegate_.OnPlatformViewCreated(std::move(surface));
}
1
2
3
4
5
6
7
8
9
10
11
12
13
主要实现的功能就是将native_window设置到surface中,再将这个surface通知到delegate(也就是Shell)中。也就是说,PlatformView主要起到一个沟通Surface和Shell的作用。

1.2.3 Rasterizer
Rasterizer是Shell的一个组成部分,运行在GPU线程中。每个Shell只能拥有一个Rasterizer实例。Rasterizer持有一个当前活动的在屏幕中显示的绘制Surface。Rasterizer在这个Surface上绘制从Engine中提交的layer tree。合成器上下文和屏上绘制Surface是Rasterizer的主要组成部分:合成器上下文包含绘制帧的所有必要的GPU状态。

on_create_rasterizer方法在AndroidShellHolder的构造方法中给出:

// ./shell/platform/android/android_shell_holder.cc
Shell::CreateCallback<Rasterizer> on_create_rasterizer = [](Shell& shell) {
return std::make_unique<Rasterizer>(shell, shell.GetTaskRunners());
};
1
2
3
4
// ./shell/common/rasterizer.cc
Rasterizer::Rasterizer(Delegate& delegate, TaskRunners task_runners)
: Rasterizer(delegate,
std::move(task_runners),
std::make_unique<flutter::CompositorContext>(
delegate.GetFrameBudget())) {}

Rasterizer::Rasterizer(
Delegate& delegate,
TaskRunners task_runners,
std::unique_ptr<flutter::CompositorContext> compositor_context)
: delegate_(delegate),
task_runners_(std::move(task_runners)),
compositor_context_(std::move(compositor_context)),
user_override_resource_cache_bytes_(false),
weak_factory_(this) {
FML_DCHECK(compositor_context_);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
在构造方法中创建了一个CompositorContext,这个类会在后续的文章中分析渲染机制时进行分析。

1.2.4 ShellIOManager
// ./shell/common/shell_io_manager.cc
class ShellIOManager final : public IOManager {

void NotifyResourceContextAvailable(sk_sp<GrContext> resource_context);
void UpdateResourceContext(sk_sp<GrContext> resource_context);

fml::WeakPtr<GrContext> GetResourceContext() const override;
fml::RefPtr<flutter::SkiaUnrefQueue> GetSkiaUnrefQueue() const override;
}
1
2
3
4
5
6
7
8
9
ShellIOManager继承自IOManager类。IOManager是管理获取GrContext资源和Skia队列的方法的接口类。这两者都属于图形绘制相关内容,在后续文章中进行分析。
NotifyResourceContextAvailable和UpdateResourceContext方法是通知GrContext创建和获取的方法。

1.2.5 Engine
Engine类是Shell的组成部分之一,运行于UI线程。其主要功能是管理根Isolate和它的运行时。每个Shell只能拥有一个Engine实例。Flutter应用的根Isolate会获取“窗口”绑定。通过这些绑定,一个用可以调度帧、推送layer tree用于渲染、请求解压图片并提交到GPU等等。
Engine管理根Isolate的全部生命周期。当Engine被回收时,其持有者会认为根Isolate已经关闭且资源已经被回收。
下面看一下Engine的构造方法:

// ./shell/common/engine.cc
Engine::Engine(Delegate& delegate,
const PointerDataDispatcherMaker& dispatcher_maker,
DartVM& vm,
fml::RefPtr<const DartSnapshot> isolate_snapshot,
TaskRunners task_runners,
const WindowData window_data,
Settings settings,
std::unique_ptr<Animator> animator,
fml::WeakPtr<IOManager> io_manager,
fml::RefPtr<SkiaUnrefQueue> unref_queue,
fml::WeakPtr<SnapshotDelegate> snapshot_delegate)
: delegate_(delegate),
settings_(std::move(settings)),
animator_(std::move(animator)),
activity_running_(true),
have_surface_(false),
image_decoder_(task_runners,
vm.GetConcurrentWorkerTaskRunner(),
io_manager),
task_runners_(std::move(task_runners)),
weak_factory_(this) {
// Runtime controller is initialized here because it takes a reference to this
// object as its delegate. The delegate may be called in the constructor and
// we want to be fully initilazed by that point.
runtime_controller_ = std::make_unique<RuntimeController>(
*this, // runtime delegate
&vm, // VM
std::move(isolate_snapshot), // isolate snapshot
task_runners_, // task runners
std::move(snapshot_delegate),
std::move(io_manager), // io manager
std::move(unref_queue), // Skia unref queue
image_decoder_.GetWeakPtr(), // image decoder
settings_.advisory_script_uri, // advisory script uri
settings_.advisory_script_entrypoint, // advisory script entrypoint
settings_.idle_notification_callback, // idle notification callback
window_data, // window data
settings_.isolate_create_callback, // isolate create callback
settings_.isolate_shutdown_callback, // isolate shutdown callback
settings_.persistent_isolate_data // persistent isolate data
);

pointer_data_dispatcher_ = dispatcher_maker(*this);
}
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
当Engine创建时,会立刻创建一个RuntimeController,在RuntimeController中又会立刻创建一个DartIsolate。

2. 启动流程
启动流程从FLutterActivity的onStart方法开始分析。

先看onStart方法代码:

// ./shell/platform/android/io/flutter/embedding/android/FlutterActivity.java
@Override
protected void onStart() {
super.onStart();
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_START);
delegate.onStart();
}
1
2
3
4
5
6
7
该方法先将生命周期调整为ON_START状态,然后调用FlutterActivityAndFragmentDelegate的onStart方法。

下面是FlutterActivityAndFragmentDelegate的onStart方法:

// ./shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java
void onStart() {
ensureAlive();
doInitialFlutterViewRun();
}
1
2
3
4
5
这里先做了活动确认,后启动FlutterView内的Dart。

// ./shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java
private void doInitialFlutterViewRun() {

if (flutterEngine.getDartExecutor().isExecutingDart()) {
// No warning is logged because this situation will happen on every config
// change if the developer does not choose to retain the Fragment instance.
// So this is expected behavior in many cases.
return;
}

// Configure the Dart entrypoint and execute it.
DartExecutor.DartEntrypoint entrypoint =
new DartExecutor.DartEntrypoint(
host.getAppBundlePath(), host.getDartEntrypointFunctionName());
flutterEngine.getDartExecutor().executeDartEntrypoint(entrypoint); // [9]
}

————————————————
版权声明:本文为CSDN博主「董小虫」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/dongzhong1990/article/details/105678124

ORM框架的发展历程 - 掘金

mikel阅读(649)

来源: ORM框架的发展历程 – 掘金

一、ORM框架的发展历程

image.png

1. JDBC操作

1.1 JDBC操作的特点

最初的时候我们肯定是直接通过jdbc来直接操作数据库的,本地数据库我们有一张t_user表,那么我们的操作流程是
复制代码
// 注册 JDBC 驱动
Class.forName("com.mysql.cj.jdbc.Driver");

// 打开连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatisdb?characterEncoding=utf-8&serverTimezone=UTC", "root", "123456");

// 执行查询
stmt = conn.createStatement();
String sql = "SELECT id,user_name,real_name,password,age,d_id from t_user where id = 1";
ResultSet rs = stmt.executeQuery(sql);

// 获取结果集
while (rs.next()) {
    Integer id = rs.getInt("id");
    String userName = rs.getString("user_name");
    String realName = rs.getString("real_name");
    String password = rs.getString("password");
    Integer did = rs.getInt("d_id");
    user.setId(id);
    user.setUserName(userName);
    user.setRealName(realName);
    user.setPassword(password);
    user.setDId(did);

    System.out.println(user);
}
复制代码

具体的操作步骤是,首先在pom.xml中引入MySQL的驱动依赖,注意MySQL数据库的版本

  1. Class.forName注册驱动
  2. 获取一个Connection对象
  3. 创建一个Statement对象
  4. execute()方法执行SQL语句,获取ResultSet结果集
  5. 通过ResultSet结果集给POJO的属性赋值
  6. 最后关闭相关的资源
这种实现方式首先给我们的感觉就是操作步骤比较繁琐,在复杂的业务场景中会更麻烦。尤其是我们需要自己来维护管理资源的连接,如果忘记了,就很可能造成数据库服务连接耗尽。同时我们还能看到具体业务的SQL语句直接在代码中写死耦合性增强。每个连接都会经历这几个步骤,重复代码很多,总结上面的操作的特点:
复制代码
  1. 代码重复
  2. 资源管理
  3. 结果集处理
  4. SQL耦合

针对这些问题我们可以自己尝试解决下

1.2 JDBC优化1.0

针对常规jdbc操作的特点,我们可以先从代码重复和资源管理方面来优化,我们可以创建一个工具类来专门处理这个问题
复制代码
public class DBUtils {

    private static final String JDBC_URL = "jdbc:mysql://localhost:3306/mybatisdb?characterEncoding=utf-8&serverTimezone=UTC";
    private static final String JDBC_NAME = "root";
    private static final String JDBC_PASSWORD = "123456";

    private static  Connection conn;

    /**
     * 对外提供获取数据库连接的方法
     * @return
     * @throws Exception
     */
    public static Connection getConnection() throws Exception {
        if(conn == null){
            try{
                conn = DriverManager.getConnection(JDBC_URL,JDBC_NAME,JDBC_PASSWORD);
            }catch (Exception e){
                e.printStackTrace();
                throw new Exception();
            }
        }
        return conn;
    }

    /**
     * 关闭资源
     * @param conn
     */
    public static void close(Connection conn ){
        close(conn,null);
    }

    public static void close(Connection conn, Statement sts ){
        close(conn,sts,null);
    }

    public static void close(Connection conn, Statement sts , ResultSet rs){
        if(rs != null){
            try {
                rs.close();
            }catch (Exception e){
                e.printStackTrace();
            }
        }

        if(sts != null){
            try {
                sts.close();
            }catch (Exception e){
                e.printStackTrace();
            }
        }

        if(conn != null){
            try {
                conn.close();
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
}

复制代码

对应的jdbc操作代码可以简化如下

    /**
     *
     * 通过JDBC查询用户信息
     */
    public void queryUser(){
        Connection conn = null;
        Statement stmt = null;
        User user = new User();
        ResultSet rs = null;
        try {
            // 注册 JDBC 驱动
            // Class.forName("com.mysql.cj.jdbc.Driver");

            // 打开连接
            conn = DBUtils.getConnection();
            // 执行查询
            stmt = conn.createStatement();
            String sql = "SELECT id,user_name,real_name,password,age,d_id from t_user where id = 1";
            rs = stmt.executeQuery(sql);

            // 获取结果集
            while (rs.next()) {
                Integer id = rs.getInt("id");
                String userName = rs.getString("user_name");
                String realName = rs.getString("real_name");
                String password = rs.getString("password");
                Integer did = rs.getInt("d_id");
                user.setId(id);
                user.setUserName(userName);
                user.setRealName(realName);
                user.setPassword(password);
                user.setDId(did);
                System.out.println(user);
            }

        } catch (SQLException se) {
            se.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            DBUtils.close(conn,stmt,rs);
        }
    }

   /**
     * 通过JDBC实现添加用户信息的操作
     */
    public void addUser(){
        Connection conn = null;
        Statement stmt = null;
        try {
            // 打开连接
            conn = DBUtils.getConnection();
            // 执行查询
            stmt = conn.createStatement();
            String sql = "INSERT INTO T_USER(user_name,real_name,password,age,d_id)values('wangwu','王五','111',22,1001)";
            int i = stmt.executeUpdate(sql);
            System.out.println("影响的行数:" + i);
        } catch (SQLException se) {
            se.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            DBUtils.close(conn,stmt);
        }
    }
复制代码

但是整体的操作步骤还是会显得比较复杂,这时我们可以进一步优化

1.3 JDBC优化2.0

我们可以针对DML操作的方法来优化,先解决SQL耦合的问题,在DBUtils中封装DML操作的方法

    /**
     * 执行数据库的DML操作
     * @return
     */
    public static Integer update(String sql,Object ... paramter) throws Exception{
        conn = getConnection();
        PreparedStatement ps = conn.prepareStatement(sql);
        if(paramter != null && paramter.length > 0){
            for (int i = 0; i < paramter.length; i++) {
                ps.setObject(i+1,paramter[i]);
            }
        }
        int i = ps.executeUpdate();
        close(conn,ps);
        return i;
    }
复制代码

然后在DML操作的时候我们就可以简化为如下步骤

    /**
     * 通过JDBC实现添加用户信息的操作
     */
    public void addUser(){
        String sql = "INSERT INTO T_USER(user_name,real_name,password,age,d_id)values(?,?,?,?,?)";
        try {
            DBUtils.update(sql,"wangwu","王五","111",22,1001);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
复制代码

显然这种方式会比最初的使用要简化很多,但是在查询处理的时候我们还是没有解决ResultSet结果集的处理问题,所以我们还需要继续优化

1.4 JDBC优化3.0

针对ResultSet的优化我们需要从反射和元数据两方面入手,具体如下
复制代码
    /**
     * 查询方法的简易封装
     * @param sql
     * @param clazz
     * @param parameter
     * @param <T>
     * @return
     * @throws Exception
     */
    public static <T> List<T> query(String sql, Class clazz, Object ... parameter) throws  Exception{
        conn = getConnection();
        PreparedStatement ps = conn.prepareStatement(sql);
        if(parameter != null && parameter.length > 0){
            for (int i = 0; i < parameter.length; i++) {
                ps.setObject(i+1,parameter[i]);
            }
        }
        ResultSet rs = ps.executeQuery();
        // 获取对应的表结构的元数据
        ResultSetMetaData metaData = ps.getMetaData();
        List<T> list = new ArrayList<>();
        while(rs.next()){
            // 根据 字段名称获取对应的值 然后将数据要封装到对应的对象中
            int columnCount = metaData.getColumnCount();
            Object o = clazz.newInstance();
            for (int i = 1; i < columnCount+1; i++) {
                // 根据每列的名称获取对应的值
                String columnName = metaData.getColumnName(i);
                Object columnValue = rs.getObject(columnName);
                setFieldValueForColumn(o,columnName,columnValue);
            }
            list.add((T) o);
        }
        return list;
    }

    /**
     * 根据字段名称设置 对象的属性
     * @param o
     * @param columnName
     */
    private static void setFieldValueForColumn(Object o, String columnName,Object columnValue) {
        Class<?> clazz = o.getClass();
        try {
            // 根据字段获取属性
            Field field = clazz.getDeclaredField(columnName);
            // 私有属性放开权限
            field.setAccessible(true);
            field.set(o,columnValue);
            field.setAccessible(false);
        }catch (Exception e){
            // 说明不存在 那就将 _ 转换为 驼峰命名法
            if(columnName.contains("_")){
                Pattern linePattern = Pattern.compile("_(\\w)");
                columnName = columnName.toLowerCase();
                Matcher matcher = linePattern.matcher(columnName);
                StringBuffer sb = new StringBuffer();
                while (matcher.find()) {
                    matcher.appendReplacement(sb, matcher.group(1).toUpperCase());
                }
                matcher.appendTail(sb);
                // 再次调用复制操作
                setFieldValueForColumn(o,sb.toString(),columnValue);
            }
        }
    }
复制代码

封装了以上方法后我们的查询操作就可以简化为

    /**
     *
     * 通过JDBC查询用户信息
     */
    public void queryUser(){
        try {
            String sql = "SELECT id,user_name,real_name,password,age,d_id from t_user where id = ?";
            List<User> list = DBUtils.query(sql, User.class,2);
            System.out.println(list);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
复制代码

这样一来我们在操作数据库中数据的时候就只需要关注于核心的SQL操作了。当然以上的设计还比较粗糙,,这时Apache 下的 DbUtils是一个很好的选择

作者:请叫我黄同学
链接:https://juejin.cn/post/7134306563874881549
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

「源码」基于.NET6的跨平台的低代码分布式RBAC Web 后台管理系统_科技通讯_闲暇巴

mikel阅读(638)

来源: 「源码」基于.NET6的跨平台的低代码分布式RBAC Web 后台管理系统_科技通讯_闲暇巴

今天给大家分享一款前后端分离的、代码能够自动生成的、基于.NET6的、可以跨平台的、低代码、分布式RBAC Web 后台管理系统,支持系统集成与统一认证。前端采用Vue Element Admin,后端采用服务与仓储模式——她就是RuYiAdmin。

软件架构

「源码」基于.NET6的跨平台的低代码分布式RBAC Web 后台管理系统

技术栈

「源码」基于.NET6的跨平台的低代码分布式RBAC Web 后台管理系统

项目优势

前端
框架	
	使用Vue2
	UI采用Element UI
	框架使用Vue Element Admin
	对于通用API做了统一性的封装
	对于BasePage界面与通用方法做了较为全面的封装
	支持水印
	支持锁屏
后端
框架	
	采用.Net6平台,支持跨平台
	采用 Core Web API,既可用于构建独立服务,也可以用于支撑前端业务
	支持Linux Docker
	使用Swagger作为Web API的管理工具	
	为接口与Model做了精细的注释
	使用JwtSecurity验证,防止Web API滥调
	使用AutoFac,实现依赖自动注入
	支持log4net 
	支持控制台日志输出
	支持Quartz作业调度
	支持API路由白名单
	支持Request Headers验证自由配置
	封装了统一的基类模型
	支持个性化导出Excel
	支持Excel导入常规校验
	支持相对路径、绝对路径或NAS存储
	支持AutoMapper,实现POCO与DTO自动转化
	使用地表最强ORM——SqlSugar,采用单例模式,支持多种数据库、包括国产数据库
	封装、实现了灵活高效的SQL访问底层
	支持实时消息
	封装了统一的数据返回格式
	封装了强大的查询条件,支持前端的自定义查询
	支持禁用用户实时踢出系统
	封装了前端UI、后端控制层、服务层与仓储层通用逻辑,前、后端业务代码量少,
开启极简代码时代。
	支持按钮、视图的可见性控制
	支持按钮与视图级别的颗粒授权
	支持权限下放
	支持用户行为的AOP自动监控
	支持用户行为的AOP自动鉴权
	支持业务数据与不同机构分离
	支持菜单多语
	支持配置信息热加载、热更新
	支持SQL热加载、热更新, 支持SQL与代码分离
	支持一主两从的读写分离
	支持毫秒时间戳
	支持乐观锁并发控制
	支持大数据写入、更新
	支持海量数据写入、更新
	支持数据的逻辑与物理删除
	支持线程池、多任务
	支持任务配置与调度
	支持服务熔断与降级
	支持熔断邮件告警
	支持Consul服务治理与健康检查
	支持并发限制
	是一个完整的低代码RBAC系统管理模板 
	支持自动生成代码
	支持系统集成
	支持统一认证
	业务全面使用高速缓存,系统快到飞起来
数据库	
	构建了可视化的ER关系模型
	提供一键执行的数据库初始化脚本
中间件	
	集成了Redis,支持哨兵模式集群
	集成了ActiveMQ,支持Master Slave和Broker Cluster结合的MQ集群

系统基础功能

「源码」基于.NET6的跨平台的低代码分布式RBAC Web 后台管理系统

系统设计思想

「源码」基于.NET6的跨平台的低代码分布式RBAC Web 后台管理系统

安装教程

1.安装Redis。
2.安装ActiveMQ。
3.安装Mysql数据库。作者使用的是mysql8,如果搭建基于mycat的读写分离集群,建议使用
mysql5.7。
4.安装NodeJs。
5.克隆代码。
6.初始化数据库。结构与数据位于webapi/AppData/DataModel、DataStructrue下。如果
使用低版本mysql,可能需要修改数据库初始化脚本。
7.修改配置。修改后端appsettings.json中Redis、ActiveMQ、Mysql数据库连接串。前端
mq配置位于src/constants/active-mq.js。
8.系统前端。前端管理目录位于webapi/wwwroot。推荐使用Visual Studio Code开源软件
编辑。前端的使用可以参看Vue Element Admin官网。
9.系统后端。后端采用Visual Studio 2022开发工具,请使用最新版本。

系统截图

  1. 星空粒子登录页(默认密码:123456)
  2. 系统首页
  3. 机构管理
  4. 用户管理
  5. 菜单管理
  6. 角色管理
  7. 数据字典
  8. 审计日志
  9. 导入配置
  10. 在线用户管理
  11. 在线任务调度
  12. 系统多语管理
  13. 服务器监控
  14. swagger
  15. 系统集成及统一认证
  • 系统集成
  • 统一授权
  • 一对多授权
  • 统一认证
  • 统一访问
  1. 其他
  • 数据导入合法性校验

「源码」基于.NET6的跨平台的低代码分布式RBAC Web 后台管理系统「源码」基于.NET6的跨平台的低代码分布式RBAC Web 后台管理系统

  • 极简代码风格示例
    ///
    /// 日志业务层接口
    /// 
    public interface ILogService : IBaseService
    {
    }

部署架构

  1. 传统部署架构

「源码」基于.NET6的跨平台的低代码分布式RBAC Web 后台管理系统

  1. 云平台冗余部署架构

「源码」基于.NET6的跨平台的低代码分布式RBAC Web 后台管理系统

VsCode搭建vue通用后台管理系统vue-element-admin_Thinkingcao的博客-CSDN博客_vscode做一个管理系统

mikel阅读(644)

来源: VsCode搭建vue通用后台管理系统vue-element-admin_Thinkingcao的博客-CSDN博客_vscode做一个管理系统

vue-element-admin文档: https://panjiachen.github.io/vue-element-admin-site/zh/
在线预览: https://panjiachen.github.io/vue-element-admin/#/dashboard

Vue国内克隆:
vue-admin-template:
git clone https://gitee.com/panjiachen/vue-admin-template.git

vue-element-admin:
git clone https://gitee.com/panjiachen/vue-element-admin.git

一、vue-element-admin介绍
vue-element-admin 是一个后台前端解决方案,它基于 vue 和 element-ui实现。它使用了最新的前端技术栈,内置了i18国际化解决方案,动态路由,权限验证,提炼了典型的业务模型,提供了丰富的功能组件,它可以帮助你快速搭建企业级中后台产品原型。相信不管你的需求是什么,本项目都能帮助到你。

建议

本项目的定位是后台集成方案,不太适合当基础模板来进行二次开发。因为本项目集成了很多你可能用不到的功能,会造成不少的代码冗余。如果你的项目不关注这方面的问题,也可以直接基于它进行二次开发。
集成方案: vue-element-admin
基础模板: vue-admin-template
桌面终端: electron-vue-admin
Typescript 版: vue-typescript-admin-template (鸣谢: @Armour)
Others: awesome-project

二、功能
登录 / 注销

权限验证

页面权限
指令权限
权限配置
二步登录
多环境发布

dev sit stage prod
全局功能

国际化多语言
多种动态换肤
动态侧边栏(支持多级路由嵌套)
动态面包屑
快捷导航(标签页)
Svg Sprite 图标
本地/后端 mock 数据
Screenfull全屏
自适应收缩侧边栏
编辑器

富文本
Markdown
JSON 等多格式
Excel

导出excel
导入excel
前端可视化excel
导出zip
表格

动态表格
拖拽表格
内联编辑
错误页面

401
404
組件

头像上传
返回顶部
拖拽Dialog
拖拽Select
拖拽看板
列表拖拽
SplitPane
Dropzone
Sticky
CountTo
综合实例

错误日志

Dashboard

引导页

ECharts 图表

Clipboard(剪贴复制)

Markdown2html

三、前序准备
你需要在本地安装 node和 git。本项目技术栈基于 ES2015+、vue、vuex、vue-router 、vue-cli、axios 和 element-ui,所有的请求数据都使用Mock.js进行模拟,提前了解和学习这些知识会对使用本项目有很大的帮助。

同时配套一个系列的教程文章,如何从零构建一个完整的管理后台项目,建议大家先看完这些文章再来实践本项目。

手摸手,带你用 vue 撸后台 系列一(基础篇)
手摸手,带你用 vue 撸后台 系列二(登录权限篇)
手摸手,带你用 vue 撸后台 系列三 (实战篇)
手摸手,带你用 vue 撸后台 系列四(vueAdmin 一个极简的后台基础模板)
手摸手,带你用 vue 撸后台 系列五(v4.0 新版本)
手摸手,带你封装一个 vue component
手摸手,带你优雅的使用 icon
手摸手,带你用合理的姿势使用 webpack4(上)
手摸手,带你用合理的姿势使用 webpack4(下)
四、目录结构
本项目已经为你生成了一个完整的开发框架,提供了涵盖中后台开发的各类功能和坑位,下面是整个项目的目录结构。

VSCode的个人知识管理和共享系统-JavaScript
zip

0星
超过10%的资源
21.79MB

下载
├── build # 构建相关
├── mock # 项目mock 模拟数据
├── plop-templates # 基本模板
├── public # 静态资源
│ │── favicon.ico # favicon图标
│ └── index.html # html模板
├── src # 源代码
│ ├── api # 所有请求
│ ├── assets # 主题 字体等静态资源
│ ├── components # 全局公用组件
│ ├── directive # 全局指令
│ ├── filters # 全局 filter
│ ├── icons # 项目所有 svg icons
│ ├── lang # 国际化 language
│ ├── layout # 全局 layout
│ ├── router # 路由
│ ├── store # 全局 store管理
│ ├── styles # 全局样式
│ ├── utils # 全局公用方法
│ ├── vendor # 公用vendor
│ ├── views # views 所有页面
│ ├── App.vue # 入口页面
│ ├── main.js # 入口文件 加载组件 初始化等
│ └── permission.js # 权限管理
├── tests # 测试
├── .env.xxx # 环境变量配置
├── .eslintrc.js # eslint 配置项
├── .babelrc # babel-loader 配置
├── .travis.yml # 自动化CI配置
├── vue.config.js # vue-cli 配置
├── postcss.config.js # postcss 配置
└── package.json # package.json

五、安装vue-element-admin
1. 克隆项目
git clone https://github.com/PanJiaChen/vue-element-admin.git
1
2. 进入项目目录
cd vue-element-admin
1
3.安装依赖
npm install
1
4. 设置淘宝镜像源
建议不要用 cnpm 安装 会有各种诡异的bug 可以通过如下操作解决 npm 下载速度慢的问题

npm install –registry=https://registry.npm.taobao.org
1
5. 本地开发 启动项目
npm run dev
1
六、访问
启动完成后会自动打开浏览器访问 http://localhost:9527, 你看到下面的页面就代表操作成功了。

————————————————
版权声明:本文为CSDN博主「Thinkingcao」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Thinkingcao/article/details/107765365

Error: Cannot find module '@vue/cli-plugin-babel'_前端段的博客-CSDN博客_@vue/cli-plugin-babel

mikel阅读(942)

来源: Error: Cannot find module ‘@vue/cli-plugin-babel’_前端段的博客-CSDN博客_@vue/cli-plugin-babel

运行npm run serve报错:
Error: Cannot find module ‘@vue/cli-plugin-babel’

解决办法:
1.安装开发依赖:
npm install babel-plugin-import -D

2.查看package.json里面的版本号,安装对应版本试试。
————————————————
版权声明:本文为CSDN博主「前端段」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/hkduan/article/details/105772792

使用手机控制PPT播放实现方法(含源码)_humbinal的博客-CSDN博客

mikel阅读(1153)

来源: 使用手机控制PPT播放实现方法(含源码)_humbinal的博客-CSDN博客

本文教你如何实现通过手机控制PC的幻灯片放映。

本篇文章是基于我的上一篇文章,上一篇文章中分享了这样一个小工具,这里把源码什么的分享出来。

本工具的制作使用Node.JS以及WebSocket技术,大致如下:

首先是搭建Node.JS平台,这里就不在说了,网上教程很多,我们从模块安装开始:

1.创建项目文件夹,在该文件夹下运行CMD,输入:

nmp install express

完成后继续:

npm install socket.io

以及:

npm install ejs

到这里我们需要使用的模块就装好了,我使用了EJS作为模板引擎配合express使用,socket.io用来进行WebSocket通信。

下载下面的附件一,解压后修改public文件夹中js文件夹下的script.js(第二行) 中的网址为你的网址即可。

下载附件二修改文件中src文件中remote.js(第三行)中的网址为你的网址即可。

这是启动你的服务就可以开始运行了,由于文件稍多,这里就不在一一分析具体的代码了,直接下载查看吧,有什么问题欢迎留言咨询。

这是可以手机打开sample.com;

放映PPT的电脑使用IE浏览器打开附件二中的ppt.html ;

具体使用详情见我的上一篇教程。

尽情的使用这样一款装逼神器吧!

源码下载地址:humbinal/ppt-controller (github.com)
————————————————
版权声明:本文为CSDN博主「humbinal」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u012234419/article/details/45618517

ASP.NET Core MVC 授权的扩展:自定义 Authorize Attribute 和 IApplicationModelProvide - alby - 博客园

mikel阅读(832)

来源: ASP.NET Core MVC 授权的扩展:自定义 Authorize Attribute 和 IApplicationModelProvide – alby – 博客园

一、概述

ASP.NET Core MVC 提供了基于角色( Role )、声明( Chaim ) 和策略 ( Policy ) 等的授权方式。在实际应用中,可能采用部门( Department , 本文采用用户组 Group )、职位 ( 可继续沿用 Role )、权限( Permission )的方式进行授权。要达到这个目的,仅仅通过自定义 IAuthorizationPolicyProvider 是不行的。本文通过自定义 IApplicationModelProvide 进行扩展。

二、PermissionAuthorizeAttribute : IPermissionAuthorizeData

AuthorizeAttribute 类实现了 IAuthorizeData 接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
namespace Microsoft.AspNetCore.Authorization
{
  /// <summary>
  /// Defines the set of data required to apply authorization rules to a resource.
  /// </summary>
  public interface IAuthorizeData
  {
   /// <summary>
   /// Gets or sets the policy name that determines access to the resource.
   /// </summary>
   string Policy { get; set; }
   /// <summary>
   /// Gets or sets a comma delimited list of roles that are allowed to access the resource.
   /// </summary>
   string Roles { get; set; }
   /// <summary>
   /// Gets or sets a comma delimited list of schemes from which user information is constructed.
   /// </summary>
   string AuthenticationSchemes { get; set; }
  }
}

使用 AuthorizeAttribute 不外乎如下几种形式:

1
2
3
4
[Authorize]
[Authorize("SomePolicy")]
[Authorize(Roles = "角色1,角色2")]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]

当然,参数还可以组合起来。另外,Roles 和 AuthenticationSchemes 的值以半角逗号分隔,是 Or 的关系;多个 Authorize 是 And 的关系;Policy 、Roles 和 AuthenticationSchemes 如果同时使用,也是 And 的关系。

如果要扩展 AuthorizeAttribute,先扩展 IAuthorizeData 增加新的属性:

1
2
3
4
5
public interface IPermissionAuthorizeData : IAuthorizeData
{
    string Groups { get; set; }
    string Permissions { get; set; }
}

然后定义 AuthorizeAttribute:

1
2
3
4
5
6
7
8
9
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public class PermissionAuthorizeAttribute : Attribute, IPermissionAuthorizeData
{
    public string Policy { get; set; }
    public string Roles { get; set; }
    public string AuthenticationSchemes { get; set; }
    public string Groups { get; set; }
    public string Permissions { get; set; }
}

现在,在 Controller 或 Action 上就可以这样使用了:

1
2
3
[PermissionAuthorize(Roles = "经理,副经理")] // 经理或部门经理
[PermissionAuthorize(Groups = "研发部,生产部", Roles = "经理"] // 研发部经理或生成部经理。Groups 和 Roles 是 `And` 的关系。
[PermissionAuthorize(Groups = "研发部,生产部", Roles = "经理", Permissions = "请假审批"] // 研发部经理或生成部经理,并且有请假审批的权限。Groups 、Roles 和 Permission 是 `And` 的关系。

数据已经准备好,下一步就是怎么提取出来。通过扩展 AuthorizationApplicationModelProvider 来实现。

三、PermissionAuthorizationApplicationModelProvider : IApplicationModelProvider

AuthorizationApplicationModelProvider 类的作用是构造 AuthorizeFilter 对象放入 ControllerModel 或 ActionModel 的 Filters 属性中。具体过程是先提取 Controller 和 Action 实现了 IAuthorizeData 接口的 Attribute,如果使用的是默认的DefaultAuthorizationPolicyProvider,则会先创建一个 AuthorizationPolicy 对象作为 AuthorizeFilter 构造函数的参数。
创建 AuthorizationPolicy 对象是由 AuthorizationPolicy 的静态方法 public static async Task<AuthorizationPolicy> CombineAsync(IAuthorizationPolicyProvider policyProvider, IEnumerable<IAuthorizeData> authorizeData) 来完成的。该静态方法会解析 IAuthorizeData 的数据,但不懂解析 IPermissionAuthorizeData

因为 AuthorizationApplicationModelProvider 类对 AuthorizationPolicy.CombineAsync 静态方法有依赖,这里不得不做一个类似的 PermissionAuthorizationApplicationModelProvider 类,在本类实现 CombineAsync 方法。暂且不论该方法放在本类是否合适的问题。

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
       public static AuthorizeFilter GetFilter(IAuthorizationPolicyProvider policyProvider, IEnumerable<IAuthorizeData> authData)
       {
           // The default policy provider will make the same policy for given input, so make it only once.
           // This will always execute synchronously.
           if (policyProvider.GetType() == typeof(DefaultAuthorizationPolicyProvider))
           {
               var policy = CombineAsync(policyProvider, authData).GetAwaiter().GetResult();
               return new AuthorizeFilter(policy);
           }
           else
           {
               return new AuthorizeFilter(policyProvider, authData);
           }
       }
       private static async Task<AuthorizationPolicy> CombineAsync(IAuthorizationPolicyProvider policyProvider, IEnumerable<IAuthorizeData> authorizeData)
       {
           if (policyProvider == null)
           {
               throw new ArgumentNullException(nameof(policyProvider));
           }
           if (authorizeData == null)
           {
               throw new ArgumentNullException(nameof(authorizeData));
           }
           var policyBuilder = new AuthorizationPolicyBuilder();
           var any = false;
           foreach (var authorizeDatum in authorizeData)
           {
               any = true;
               var useDefaultPolicy = true;
               if (!string.IsNullOrWhiteSpace(authorizeDatum.Policy))
               {
                   var policy = await policyProvider.GetPolicyAsync(authorizeDatum.Policy);
                   if (policy == null)
                   {
                       //throw new InvalidOperationException(Resources.FormatException_AuthorizationPolicyNotFound(authorizeDatum.Policy));
                       throw new InvalidOperationException(nameof(authorizeDatum.Policy));
                   }
policyBuilder.Combine(policy);
                   useDefaultPolicy = false;
               }
               var rolesSplit = authorizeDatum.Roles?.Split(',');
               if (rolesSplit != null && rolesSplit.Any())
               {
                   var trimmedRolesSplit = rolesSplit.Where(r => !string.IsNullOrWhiteSpace(r)).Select(r => r.Trim());
                   policyBuilder.RequireRole(trimmedRolesSplit);
                   useDefaultPolicy = false;
               }
               if(authorizeDatum is IPermissionAuthorizeData permissionAuthorizeDatum )
               {
                   var groupsSplit = permissionAuthorizeDatum.Groups?.Split(',');
                   if (groupsSplit != null && groupsSplit.Any())
                   {
                       var trimmedGroupsSplit = groupsSplit.Where(r => !string.IsNullOrWhiteSpace(r)).Select(r => r.Trim());
                       policyBuilder.RequireClaim("Group", trimmedGroupsSplit); // TODO: 注意硬编码
                       useDefaultPolicy = false;
                   }
                   var permissionsSplit = permissionAuthorizeDatum.Permissions?.Split(',');
                   if (permissionsSplit != null && permissionsSplit.Any())
                   {
                       var trimmedPermissionsSplit = permissionsSplit.Where(r => !string.IsNullOrWhiteSpace(r)).Select(r => r.Trim());
                       policyBuilder.RequireClaim("Permission", trimmedPermissionsSplit);// TODO: 注意硬编码
                       useDefaultPolicy = false;
                   }
               }
               var authTypesSplit = authorizeDatum.AuthenticationSchemes?.Split(',');
               if (authTypesSplit != null && authTypesSplit.Any())
               {
                   foreach (var authType in authTypesSplit)
                   {
                       if (!string.IsNullOrWhiteSpace(authType))
                       {
                           policyBuilder.AuthenticationSchemes.Add(authType.Trim());
                       }
                   }
               }
               if (useDefaultPolicy)
               {
policyBuilder.Combine(await policyProvider.GetDefaultPolicyAsync());
               }
           }
           return any ? policyBuilder.Build() : null;
       }

if(authorizeDatum is IPermissionAuthorizeData permissionAuthorizeDatum ) 为扩展部分。

四、Startup

注册 PermissionAuthorizationApplicationModelProvider 服务,需要在 AddMvc 之后替换掉 AuthorizationApplicationModelProvider 服务。

1
2
services.AddMvc();
services.Replac(ServiceDescriptor.Transient<IApplicationModelProvider,PermissionAuthorizationApplicationModelProvider>());

五、Jwt 示例

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
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
    private readonly JwtSecurityTokenHandler _tokenHandler = new JwtSecurityTokenHandler();
    [HttpGet]
    [Route("SignIn")]
    public async Task<ActionResult<string>> SignIn()
    {
        var user = new ClaimsPrincipal(new ClaimsIdentity(new[]
        {
            // 备注:Claim Type: Group 和 Permission 这里使用的是硬编码,应该定义为类似于 ClaimTypes.Role 的常量;另外,下列模拟数据不一定合逻辑。
            new Claim(ClaimTypes.Name, "Bob"),
            new Claim(ClaimTypes.Role, "经理"),  // 注意:不能使用逗号分隔来达到多个角色的目的,下同。
            new Claim(ClaimTypes.Role, "副经理"),
            new Claim("Group", "研发部"),
            new Claim("Group", "生产部"),
            new Claim("Permission", "请假审批"),
            new Claim("Permission", "权限1"),
            new Claim("Permission", "权限2"),
        }, JwtBearerDefaults.AuthenticationScheme));
        var token = new JwtSecurityToken(
            "SignalRAuthenticationSample",
            "SignalRAuthenticationSample",
            user.Claims,
            expires: DateTime.UtcNow.AddDays(30),
            signingCredentials: SignatureHelper.GenerateSigningCredentials("1234567890123456"));
        return _tokenHandler.WriteToken(token);
    }
    [HttpGet]
    [Route("Test")]
    [PermissionAuthorize(Groups = "研发部,生产部", Roles = "经理", Permissions = "请假审批"] // 研发部经理或生成部经理,并且有请假审批的权限。Groups 、Roles 和 Permission 是 `And` 的关系。
    public async Task<ActionResult<IEnumerable<string>>> Test()
    {
        var user = HttpContext.User;
        return new string[] { "value1", "value2" };
    }
 }

六、问题

AuthorizeFilter 类显示实现了 IFilterFactory 接口的 CreateInstance 方法:

1
2
3
4
5
6
7
8
9
10
11
12
IFilterMetadata IFilterFactory.CreateInstance(IServiceProvider serviceProvider)
{
    if (Policy != null || PolicyProvider != null)
    {
        // The filter is fully constructed. Use the current instance to authorize.
        return this;
    }

    Debug.Assert(AuthorizeData != null);
    var policyProvider = serviceProvider.GetRequiredService<IAuthorizationPolicyProvider>();
    return AuthorizationApplicationModelProvider.GetFilter(policyProvider, AuthorizeData);
}

竟然对 AuthorizationApplicationModelProvider.GetFilter 静态方法产生了依赖。庆幸的是,如果通过 AuthorizeFilter(IAuthorizationPolicyProvider policyProvider, IEnumerable<IAuthorizeData> authorizeData) 或 AuthorizeFilter(AuthorizationPolicy policy) 创建 AuthorizeFilter 对象不会产生什么不良影响。

七、下一步

[PermissionAuthorize(Groups = "研发部,生产部", Roles = "经理", Permissions = "请假审批"] 这种形式还是不够灵活,哪怕用多个 Attribute, And 和 Or 的逻辑组合不一定能满足需求。可以在 IPermissionAuthorizeData 新增一个 Rule 属性,实现类似的效果:

1
[PermissionAuthorize(Rule = "(Groups:研发部,生产部)&&(Roles:请假审批||Permissions:超级权限)"]

通过 Rule 计算复杂的授权。

八、如果通过自定义 IAuthorizationPolicyProvider 实现?

另一种方式是自定义 IAuthorizationPolicyProvider ,不过还需要自定义 AuthorizeFilter。因为当不是使用 DefaultAuthorizationPolicyProvider 而是自定义 IAuthorizationPolicyProvider 时,AuthorizationApplicationModelProvider(或前文定义的 PermissionAuthorizationApplicationModelProvider)会使用 AuthorizeFilter(IAuthorizationPolicyProvider policyProvider, IEnumerable<IAuthorizeData> authorizeData) 创建 AuthorizeFilter 对象,而不是 AuthorizeFilter(AuthorizationPolicy policy)。这会造成 AuthorizeFilter 对象在 OnAuthorizationAsync 时会间接调用 AuthorizationPolicy.CombineAsync 静态方法。

这可以说是一个设计上的缺陷,不应该让 AuthorizationPolicy.CombineAsync 静态方法存在,哪怕提供个 IAuthorizationPolicyCombiner 也好。另外,上文提到的 AuthorizationApplicationModelProvider.GetFilter 静态方法同样不是一种好的设计。等微软想通吧。

参考资料

https://docs.microsoft.com/zh-cn/aspnet/core/security/authorization/iauthorizationpolicyprovider?view=aspnetcore-2.1

 

排版问题:http://blog.tubumu.com/2018/11/28/aspnetcore-mvc-extend-authorization/

使用nginx后net core无法获取ip问题_qq_34897745的博客-CSDN博客

mikel阅读(765)

来源: 使用nginx后net core无法获取ip问题_qq_34897745的博客-CSDN博客

使用了nginx后net core获取ip地址居然全部是本地的地址,不是外网的地址

这是因为nginx转发了一次后,我们直接使用常规获取ip地址的方式就是本地的地址了

 

瞧瞧nginx的配置,然后找获取外网ip的方法

 

 

这里我们可以看到,我们配了一个real-ip,nginx会转发给你,通过请求的header获取就行了

context.HttpContext.Request.Headers[“X-Real-IP”].FirstOrDefault();

————————————————
版权声明:本文为CSDN博主「qq_34897745」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_34897745/article/details/106093714

net core中获取用户请求ip地址_qq_34897745的博客-CSDN博客_.net core 获取请求ip

mikel阅读(793)

来源: net core中获取用户请求ip地址_qq_34897745的博客-CSDN博客_.net core 获取请求ip

方法一:通过注入来获取

先添加一个依赖注入

services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
在控制器里边使用构造函数注入

private readonly IHttpContextAccessor _httpContextAccessor;
public TestController( IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
获取

//获取ip地址
string ipaddress = _httpContextAccessor.HttpContext.Connection.RemoteIpAddress.ToString();

方法二:直接获取

public void OnActionExecuting(ActionExecutingContext context)
{
//获取ip地址
string ipaddress = context.HttpContext.Connection.RemoteIpAddress.ToString();
}

但是这两种写法,使用了nginx后是无法访问的

使用了nginx后无法获取ip问题:

https://blog.csdn.net/qq_34897745/article/details/106093714
————————————————
版权声明:本文为CSDN博主「qq_34897745」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_34897745/article/details/106093610

ASP.NET Core 认证与授权[6]:授权策略是怎么执行的? - 雨夜朦胧 - 博客园

mikel阅读(803)

来源: ASP.NET Core 认证与授权[6]:授权策略是怎么执行的? – 雨夜朦胧 – 博客园

在上一章中,详细介绍了 ASP.NET Core 中的授权策略,在需要授权时,只需要在对应的Controler或者Action上面打上[Authorize]特性,并指定要执行的策略名称即可,但是,授权策略是怎么执行的呢?怀着一颗好奇的心,忍不住来探索一下它的执行流程。

目录

  1. MVC中的授权
  2. IPolicyEvaluator
  3. IAuthorizationService

在《(上一章》中提到,AuthorizeAttribute只是一个简单的实现了IAuthorizeData接口的特性,并且在 ASP.NET Core 授权系统中并没有使用到它。我们知道在认证中,还有一个UseAuthentication扩展方法来激活认证系统,但是在授权中并没有类似的机制。

这是因为当我们使用[Authorize]通常是在MVC中,由MVC来负责激活授权系统。本来在这个系列的文章中,我并不想涉及到MVC的知识,但是为了能更好的理解授权系统的执行,就来简单介绍一下MVC中与授权相关的知识。

MVC中的授权

当我们使用MVC时,首先会调用MVC的AddMvc扩展方法,用来注册一些MVC相关的服务:

public static IMvcBuilder AddMvc(this IServiceCollection services)
{
    var builder = services.AddMvcCore();

    builder.AddAuthorization();

    ...
}

public static IMvcCoreBuilder AddAuthorization(this IMvcCoreBuilder builder)
{
    AddAuthorizationServices(builder.Services);
    return builder;
}

internal static void AddAuthorizationServices(IServiceCollection services)
{
    services.AddAuthenticationCore();
    services.AddAuthorization();
    services.AddAuthorizationPolicyEvaluator();

    services.TryAddEnumerable(
        ServiceDescriptor.Transient<IApplicationModelProvider, AuthorizationApplicationModelProvider>());
}

在上面AddAuthorizationServices中的前三个方法都属于 ASP.NET Core 《Security》项目中提供的扩展方法,其中前两个在前面几章已经介绍过了,对于AddAuthorizationPolicyEvaluator放到后面再来介绍,我们先来看一下MVC中的AuthorizationApplicationModelProvider

AuthorizationApplicationModelProvider

在MVC中有一个ApplicationModel的概念,它用来封装ControllerFilterApiExplorer等。对应的,在MVC中还提供了一系列的ApplicationModelProvider来初始化ApplicationModel的各个部分,而AuthorizationApplicationModelProvider就是用来初始化与授权相关的部分。

public class AuthorizationApplicationModelProvider : IApplicationModelProvider
{
    public void OnProvidersExecuting(ApplicationModelProviderContext context)
    {
        foreach (var controllerModel in context.Result.Controllers)
        {
            var controllerModelAuthData = controllerModel.Attributes.OfType<IAuthorizeData>().ToArray();
            if (controllerModelAuthData.Length > 0)
            {
                controllerModel.Filters.Add(GetFilter(_policyProvider, controllerModelAuthData));
            }
            foreach (var attribute in controllerModel.Attributes.OfType<IAllowAnonymous>())
            {
                controllerModel.Filters.Add(new AllowAnonymousFilter());
            }
            foreach (var actionModel in controllerModel.Actions)
            {
                var actionModelAuthData = actionModel.Attributes.OfType<IAuthorizeData>().ToArray();
                if (actionModelAuthData.Length > 0)
                {
                    actionModel.Filters.Add(GetFilter(_policyProvider, actionModelAuthData));
                }
                foreach (var attribute in actionModel.Attributes.OfType<IAllowAnonymous>())
                {
                    actionModel.Filters.Add(new AllowAnonymousFilter());
                }
            }
        }
    }
}

如上,首先查找每个Controller中实现了IAuthorizeData接口的特性,然后将其转化为AuthorizeFilter并添加到Controller的Filter集合中,紧接着再查找实现了IAllowAnonymous接口的特性,将其转化为AllowAnonymousFilter过滤器也添加到Filter集合中,然后以同样的逻辑查找Action上的特性并添加到Action的Filter集合中。

其中的关键点就是将IAuthorizeData(也就是通过我们熟悉的[Authorize]特性)转化为MVC中的AuthorizeFilter过滤器:

public static AuthorizeFilter GetFilter(IAuthorizationPolicyProvider policyProvider, IEnumerable<IAuthorizeData> authData)
{
    if (policyProvider.GetType() == typeof(DefaultAuthorizationPolicyProvider))
    {
        var policy = AuthorizationPolicy.CombineAsync(policyProvider, authData).GetAwaiter().GetResult();
        return new AuthorizeFilter(policy);
    }
    else
    {
        return new AuthorizeFilter(policyProvider, authData);
    }
}

CombineAsync在上一章的《AuthorizationPolicy》中已经介绍过了,我们往下看看AuthorizeFilter的实现。

AuthorizeFilter

在MVC中有一个AuthorizeFilter过滤器,类似我们在ASP.NET 4.x中所熟悉的[Authorize],它实现了IAsyncAuthorizationFilter接口,定义如下:

public class AuthorizeFilter : IAsyncAuthorizationFilter, IFilterFactory
{
    public AuthorizeFilter(AuthorizationPolicy policy) {}
    public AuthorizeFilter(IAuthorizationPolicyProvider policyProvider, IEnumerable<IAuthorizeData> authorizeData) : this(authorizeData) {}
    public AuthorizeFilter(IEnumerable<IAuthorizeData> authorizeData) {}

    public IEnumerable<IAuthorizeData> AuthorizeData { get; }
    public AuthorizationPolicy Policy { get; }

    public virtual async Task OnAuthorizationAsync(AuthorizationFilterContext context)
    {
        var effectivePolicy = Policy;
        if (effectivePolicy == null)
        {
            effectivePolicy = await AuthorizationPolicy.CombineAsync(PolicyProvider, AuthorizeData);
        }
        var policyEvaluator = context.HttpContext.RequestServices.GetRequiredService<IPolicyEvaluator>();
        var authenticateResult = await policyEvaluator.AuthenticateAsync(effectivePolicy, context.HttpContext);
        if (context.Filters.Any(item => item is IAllowAnonymousFilter))
        {
            return;
        }
        var authorizeResult = await policyEvaluator.AuthorizeAsync(effectivePolicy, authenticateResult, context.HttpContext, context);

        ... // 如果授权失败,返回ChallengeResult或ForbidResult
    }
}

AuthorizeFilter的OnAuthorizationAsync方法会在Action执行之前触发,其调用IPolicyEvaluator来完成授权,将执行流程切回到 ASP.NET Core 授权系统中。关于MVC中IApplicationModelProvider以及Filter的概念,在以后MVC系列的文章中再来详细介绍,下面就继续介绍 ASP.NET Core 的授权系统,也就是《Security》项目。

IPolicyEvaluator

IPolicyEvaluator是MVC调用授权系统的入口点,其定义如下:

public interface IPolicyEvaluator
{
    Task<AuthenticateResult> AuthenticateAsync(AuthorizationPolicy policy, HttpContext context);
    Task<PolicyAuthorizationResult> AuthorizeAsync(AuthorizationPolicy policy, AuthenticateResult authenticationResult, HttpContext context, object resource);
}

在上面介绍的AddMVC中,调用了AddAuthorizationPolicyEvaluator扩展方法,它有如下定义:

public static class PolicyServiceCollectionExtensions
{
    public static IServiceCollection AddAuthorizationPolicyEvaluator(this IServiceCollection services)
    {
        services.TryAdd(ServiceDescriptor.Transient<IPolicyEvaluator, PolicyEvaluator>());
        return services;
    }
}

由此可知IPolicyEvaluator的默认实现为PolicyEvaluator,我们就从它入手,来一步一步解剖 ASP.NET Core 授权系统的执行步骤。

AuthorizeFilter中,依次调到了AuthenticateAsyncAuthorizeAsync方法,我们就一一来看。

AuthenticateAsync(AuthenticationSchemes)

为什么还有一个AuthenticateAsync方法呢,这不是在认证阶段执行的吗?我们看下它的实现:

public class PolicyEvaluator : IPolicyEvaluator
{
    public virtual async Task<AuthenticateResult> AuthenticateAsync(AuthorizationPolicy policy, HttpContext context)
    {
        if (policy.AuthenticationSchemes != null && policy.AuthenticationSchemes.Count > 0)
        {
            ClaimsPrincipal newPrincipal = null;
            foreach (var scheme in policy.AuthenticationSchemes)
            {
                var result = await context.AuthenticateAsync(scheme);
                if (result != null && result.Succeeded)
                {
                    newPrincipal = SecurityHelper.MergeUserPrincipal(newPrincipal, result.Principal);
                }
            }

            if (newPrincipal != null)
            {
                context.User = newPrincipal;
                return AuthenticateResult.Success(new AuthenticationTicket(newPrincipal, string.Join(";", policy.AuthenticationSchemes)));
            }
            else
            {
                context.User = new ClaimsPrincipal(new ClaimsIdentity());
                return AuthenticateResult.NoResult();
            }
        }

        return (context.User?.Identity?.IsAuthenticated ?? false) 
            ? AuthenticateResult.Success(new AuthenticationTicket(context.User, "context.User"))
            : AuthenticateResult.NoResult();
    }
}

在《上一章》中,我们知道在AuthorizationPolicy中有AuthenticationSchemesIAuthorizationRequirement两个属性,并详细介绍介绍了Requirement,但是没有提到AuthenticationSchemes的调用。

那么,看到这里,也就大概明白了,它与Requirements的执行是完全独立的,并在它之前执行,用于重置Claims,那么为什么要重置呢?

在认证的章节介绍过,在认证阶段,只会执行默认的认证Scheme,context.User就是使用context.AuthenticateAsync(DefaultAuthenticateScheme)来赋值的,当我们希望使用非默认的Scheme,或者是想合并多个认证Scheme的Claims时,就需要使用基于Scheme的授权来重置Claims了。

它的实现也很简单,直接使用我们在授权策略中指定的Schemes来依次调用认证服务的AuthenticateAsync方法,并将生成的Claims合并,最后返回我们熟悉的AuthenticateResult认证结果。

AuthorizeAsync(Requirements)

接下来再看一下PolicyEvaluatorAuthorizeAsync方法:

public class PolicyEvaluator : IPolicyEvaluator
{
    private readonly IAuthorizationService _authorization;
    public PolicyEvaluator(IAuthorizationService authorization)
    {
        _authorization = authorization;
    }

    public virtual async Task<PolicyAuthorizationResult> AuthorizeAsync(AuthorizationPolicy policy, AuthenticateResult authenticationResult, HttpContext context, object resource)
    {
        var result = await _authorization.AuthorizeAsync(context.User, resource, policy);
        if (result.Succeeded) return PolicyAuthorizationResult.Success();
        return (authenticationResult.Succeeded) ? PolicyAuthorizationResult.Forbid() : PolicyAuthorizationResult.Challenge();
    }
}

该方法会根据Requirements来完成授权,具体的实现是通过调用IAuthorizationService来实现的。

最终返回的是一个PolicyAuthorizationResult对象,并在授权失败时,根据认证结果来返回Forbid(未授权)Challenge(未登录)

public class PolicyAuthorizationResult
{
    private PolicyAuthorizationResult() { }
    public bool Challenged { get; private set; }
    public bool Forbidden { get; private set; }
    public bool Succeeded { get; private set; }
}

IAuthorizationService

然后就到了授权的核心对象AuthorizationService,也可以称为授权的外交官,我们也可以直接在应用代码中调用该对象来实现授权,它有如下定义:

public interface IAuthorizationService
{    
    Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource, string policyName);
    Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource, IEnumerable<IAuthorizationRequirement> requirements);
}

AuthorizeAsync中还涉及到一个resource对象,用来实现面向资源的授权,放在《下一章》中再来介绍,而在本章与《前一章》的示例中,该值均为null

ASP.NET Core 中还为IAuthorizationService提供了几个扩展方法:

public static class AuthorizationServiceExtensions
{
    public static Task<AuthorizationResult> AuthorizeAsync(this IAuthorizationService service, ClaimsPrincipal user, string policyName) {}
    public static Task<AuthorizationResult> AuthorizeAsync(this IAuthorizationService service, ClaimsPrincipal user, AuthorizationPolicy policy) {}
    public static Task<AuthorizationResult> AuthorizeAsync(this IAuthorizationService service, ClaimsPrincipal user, object resource, IAuthorizationRequirement requirement) {}
    public static Task<AuthorizationResult> AuthorizeAsync(this IAuthorizationService service, ClaimsPrincipal user, object resource, AuthorizationPolicy policy) {}
}

其默认实现为DefaultAuthorizationService:

public class DefaultAuthorizationService : IAuthorizationService
{
    private readonly AuthorizationOptions _options;
    private readonly IAuthorizationHandlerContextFactory _contextFactory;
    private readonly IAuthorizationHandlerProvider _handlers;
    private readonly IAuthorizationEvaluator _evaluator;
    private readonly IAuthorizationPolicyProvider _policyProvider;

    public async Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource, string policyName)
    {        
        var policy = await _policyProvider.GetPolicyAsync(policyName);
        return await this.AuthorizeAsync(user, resource, policy);
    }

    public async Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource, IEnumerable<IAuthorizationRequirement> requirements)
    {
        var authContext = _contextFactory.CreateContext(requirements, user, resource);
        var handlers = await _handlers.GetHandlersAsync(authContext);
        foreach (var handler in handlers)
        {
            await handler.HandleAsync(authContext);
            if (!_options.InvokeHandlersAfterFailure && authContext.HasFailed)
            {
                break;
            }
        }
        return _evaluator.Evaluate(authContext);
    }
}

通过上面代码可以看出,在《上一章》中介绍的授权策略,在这里获取到它的Requirements,后续便不再需要了。而在AuthorizationService中是通过调用四大核心对象来完成授权,我们一一来看。

IAuthorizationPolicyProvider

由于在[Authorize]中,我们指定的是策略的名称,因此需要使用IAuthorizationPolicyProvider来根据名称获取到策略对象,默认实现为DefaultAuthorizationPolicyProvider

public class DefaultAuthorizationPolicyProvider : IAuthorizationPolicyProvider
{
    private readonly AuthorizationOptions _options;

    public Task<AuthorizationPolicy> GetDefaultPolicyAsync()
    {
        return Task.FromResult(_options.DefaultPolicy);
    }

    public virtual Task<AuthorizationPolicy> GetPolicyAsync(string policyName)
    {
        return Task.FromResult(_options.GetPolicy(policyName));
    }
}

在上一章中介绍过,我们定义的策略都保存在《AuthorizationOptions》的字典中,因此在这里只是简单的将AuthorizationOptions中的同名方法异步化。

IAuthorizationHandlerContextFactory

授权上下文是我们接触较多的对象,当我们自定义授权Handler时就会用到它,它是使用简单工厂模式来创建的:

public class DefaultAuthorizationHandlerContextFactory : IAuthorizationHandlerContextFactory
{
    public virtual AuthorizationHandlerContext CreateContext(IEnumerable<IAuthorizationRequirement> requirements, ClaimsPrincipal user, object resource)
    {
        return new AuthorizationHandlerContext(requirements, user, resource);
    }
}

授权上下文中主要包含用户的Claims和授权策略的Requirements

public class AuthorizationHandlerContext
{
    private HashSet<IAuthorizationRequirement> _pendingRequirements;
    private bool _failCalled;
    private bool _succeedCalled;

    public AuthorizationHandlerContext(IEnumerable<IAuthorizationRequirement> requirements, ClaimsPrincipal user, object resource)
    {
        Requirements = requirements; User = user; Resource = resource;
        _pendingRequirements = new HashSet<IAuthorizationRequirement>(requirements);
    }

    public virtual bool HasFailed { get { return _failCalled; } }
    public virtual bool HasSucceeded => !_failCalled && _succeedCalled && !_pendingRequirements.Any();
    public virtual void Fail()
    {
        _failCalled = true;
    }
    public virtual void Succeed(IAuthorizationRequirement requirement)
    {
        _succeedCalled = true;
        _pendingRequirements.Remove(requirement);
    }
}

如上,_pendingRequirements中保存着所有待验证的Requirements,验证成功的Requirement则从中移除。

IAuthorizationHandlerProvider

兜兜转转,终于进入到了授权的最终验证逻辑中了,首先,使用IAuthorizationHandlerProvider来获取到所有的授权Handler

IAuthorizationHandlerProvider的默认实现为DefaultAuthorizationHandlerProvider:

public class DefaultAuthorizationHandlerProvider : IAuthorizationHandlerProvider
{
    private readonly IEnumerable<IAuthorizationHandler> _handlers;

    public DefaultAuthorizationHandlerProvider(IEnumerable<IAuthorizationHandler> handlers)
    {
        _handlers = handlers;
    }

    public Task<IEnumerable<IAuthorizationHandler>> GetHandlersAsync(AuthorizationHandlerContext context)
        => Task.FromResult(_handlers);
}

在《上一章》中,我们还介绍到,我们定义的Requirement,可以直接实现IAuthorizationHandler接口,也可以单独定义Handler,但是需要注册到DI系统中去。

在默认的AuthorizationHandlerProvider中,会从DI系统中获取到我们注册的所有Handler,最终调用其HandleAsync方法。

我们在实现IAuthorizationHandler接口时,通常是继承自AuthorizationHandler<TRequirement>来实现,它有如下定义:

public abstract class AuthorizationHandler<TRequirement> : IAuthorizationHandler where TRequirement : IAuthorizationRequirement
{
    public virtual async Task HandleAsync(AuthorizationHandlerContext context)
    {
        foreach (var req in context.Requirements.OfType<TRequirement>())
        {
            await HandleRequirementAsync(context, req);
        }
    }

    protected abstract Task HandleRequirementAsync(AuthorizationHandlerContext context, TRequirement requirement);
}

如上,首先会在HandleAsync过滤出与Requirement对匹配的Handler,然后再调用其HandleRequirementAsync方法。

那我们定义的直接实现IAuthorizationHandler了接口的Requirement又是如何执行的呢?

AddAuthorization扩展方法中可以看到,默认还为IAuthorizationHandler注册了一个PassThroughAuthorizationHandler,定义如下:

public class PassThroughAuthorizationHandler : IAuthorizationHandler
{
    public async Task HandleAsync(AuthorizationHandlerContext context)
    {
        foreach (var handler in context.Requirements.OfType<IAuthorizationHandler>())
        {
            await handler.HandleAsync(context);
        }
    }
}

它负责调用该策略中所有实现了IAuthorizationHandler接口的Requirement

IAuthorizationEvaluator

最后,通过调用IAuthorizationEvaluator接口,来完成最终的授权结果,默认实现为DefaultAuthorizationEvaluator:

public class DefaultAuthorizationEvaluator : IAuthorizationEvaluator
{
    public AuthorizationResult Evaluate(AuthorizationHandlerContext context)
        => context.HasSucceeded
            ? AuthorizationResult.Success()
            : AuthorizationResult.Failed(context.HasFailed
                ? AuthorizationFailure.ExplicitFail()
                : AuthorizationFailure.Failed(context.PendingRequirements));
}

当我们在一个策略中指定多个Requirement时,只有全部验证通过时,授权上下文中的HasSucceeded才会为True,而HasFailed代表授权结果的显式失败。

这里根据授权上下文的验证结果来生成授权结果:

public class AuthorizationResult
{
    public bool Succeeded { get; private set; }
    public AuthorizationFailure Failure { get; private set; }
    public static AuthorizationResult Success() => new AuthorizationResult { Succeeded = true };
    public static AuthorizationResult Failed(AuthorizationFailure failure) => new AuthorizationResult { Failure = failure };
    public static AuthorizationResult Failed() => new AuthorizationResult { Failure = AuthorizationFailure.ExplicitFail() };
}

public class AuthorizationFailure
{
    private AuthorizationFailure() { }
    public bool FailCalled { get; private set; }
    public IEnumerable<IAuthorizationRequirement> FailedRequirements { get; private set; }
    public static AuthorizationFailure ExplicitFail()
    {
        return new AuthorizationFailure { FailCalled = true, FailedRequirements = new IAuthorizationRequirement[0] };
    }
    public static AuthorizationFailure Failed(IEnumerable<IAuthorizationRequirement> failed)
        => new AuthorizationFailure { FailedRequirements = failed };

}

整个授权流程的结构大致如下:

authorization_service

总结

通过对 ASP.NET Core 授权系统执行流程的探索,可以看出授权是主要是通过调用IAuthorizationService来完成的,而授权策略的本质是提供 Requirement ,我们完全可以使用它们两个来完成各种灵活的授权方式,而不用局限于策略。在 ASP.NET Core 中,还提供了基于资源的授权,放在《下一章》中来介绍,并会简单演示一下在一个通用权限管理系统中如何来授权。