Asp .Net Core 系列:Asp .Net Core 集成 Panda.DynamicWebApi
文章目录
- 简介
- Asp .Net Core 集成 Panda.DynamicWebApi
- 配置
- 原理
- 什么是POCO Controller?
- POCO控制器原理
- ControllerFeatureProvider
- 实现自定义判断规则
- IApplicationModelConvention
- Panda.DynamicWebApi中的实现
- ConfigureApiExplorer()
- ConfigureSelector()
- ConfigureParameters()
简介
Panda.DynamicWebApi
是一个动态生成WebApi的组件,生成的API符合Restful风格,受启发于ABP。它可以根据符合条件的类来生成WebApi,由MVC框架直接调用逻辑,无性能问题,完美兼容Swagger来构建API说明文档,与手动编写Controller相比并无区别。
应用场景:DDD架构中的应用逻辑层,可使用本组件来直接生成WebApi,而无需再用Controller来调用。
Asp .Net Core 集成 Panda.DynamicWebApi
(1)新建一个 ASP.NET Core WebApi(或MVC) 项目
(2)通过Nuget安装组件
Install-Package Panda.DynamicWebApi
(3)创建一个类命名为 AppleAppService
,实现 IDynamicWebApi
接口,并加入特性 [DynamicWebApi]
[DynamicWebApi]
public class AppleAppService: IDynamicWebApi
{private static readonly Dictionary<int,string> Apples=new Dictionary<int, string>(){[1]="Big Apple",[2]="Small Apple"};/// <summary>/// Get An Apple./// </summary>/// <param name="id"></param>/// <returns></returns>[HttpGet("{id:int}")]public string Get(int id){if (Apples.ContainsKey(id)){return Apples[id];}else{return "No Apple!";}}/// <summary>/// Get All Apple./// </summary>/// <returns></returns>public IEnumerable<string> Get(){return Apples.Values;}public void Update(UpdateAppleDto dto){if (Apples.ContainsKey(dto.Id)){Apples[dto.Id] =dto.Name;}}/// <summary>/// Delete Apple/// </summary>/// <param name="id">Apple Id</param>[HttpDelete("{id:int}")]public void Delete(int id){if (Apples.ContainsKey(id)){Apples.Remove(id);}}}
(4)在 Startup 中注册 DynamicWebApi
public void ConfigureServices(IServiceCollection services)
{// 默认配置services.AddDynamicWebApi();// 自定义配置services.AddDynamicWebApi((options) =>{// 指定全局默认的 api 前缀options.DefaultApiPrefix = "apis";/*** 清空API结尾,不删除API结尾;* 若不清空 CreatUserAsync 将变为 CreateUser*/options.RemoveActionPostfixes.Clear();/*** 自定义 ActionName 处理函数;*/options.GetRestFulActionName = (actionName) => actionName;/*** 指定程序集 配置 url 前缀为 apis* 如: http://localhost:8080/apis/User/CreateUser*/options.AddAssemblyOptions(this.GetType().Assembly, apiPreFix: "apis");/*** 指定程序集 配置所有的api请求方式都为 POST*/options.AddAssemblyOptions(this.GetType().Assembly, httpVerb: "POST");/*** 指定程序集 配置 url 前缀为 apis, 且所有请求方式都为POST* 如: http://localhost:8080/apis/User/CreateUser*/options.AddAssemblyOptions(this.GetType().Assembly, apiPreFix: "apis", httpVerb: "POST");});
}
(5)添加 Swagger
builder.Services.AddSwaggerGen(options =>{options.SwaggerDoc("v1", new OpenApiInfo() { Title = "Panda Dynamic WebApi", Version = "v1" });// TODO:一定要返回true!options.DocInclusionPredicate((docName, description) => true);var baseDirectory = System.AppDomain.CurrentDomain.BaseDirectory;var xmlFile = System.AppDomain.CurrentDomain.FriendlyName + ".xml";var xmlPath = Path.Combine(baseDirectory, xmlFile);options.IncludeXmlComments(xmlPath);});
配置
所有的配置均在对象 DynamicWebApiOptions
中,说明如下:
属性名 | 是否必须 | 说明 |
---|---|---|
DefaultHttpVerb | 否 | 默认值:POST。默认HTTP动词 |
DefaultAreaName | 否 | 默认值:空。Area 路由名称 |
DefaultApiPrefix | 否 | 默认值:api。API路由前缀 |
RemoveControllerPostfixes | 否 | 默认值:AppService/ApplicationService。类名需要移除的后缀 |
RemoveActionPostfixes | 否 | 默认值:Async。方法名需要移除的后缀 |
FormBodyBindingIgnoredTypes | 否 | 默认值:IFormFile。不通过MVC绑定到参数列表的类型。 |
原理
什么是POCO Controller?
POCO Controller是 ASP.NET Core 中的一个特性,虽然在2015年刚发布的时候就有这个特性了,可是大多数开发者都只是按原有的方式去写,而没有用到这个特性。其实,如果利用这个特性进行稍微封装后,用在SOA架构中Service层的场景中是极其便利的。这篇文章主要就是说我最近在学习使用开源AOP库AspectCore写WebApi动态代理客户端的时候,实现为普通类无添加WebApi服务的过程。
POCO控制器就是ASP.NET Core项目中所有带有Controller后缀的类、或者标记了[Controller]特性的类,虽然没有像模版项目中那样继承自Controller类,也会被识别为控制器,拥有跟普通控制器一样的功能,像下面这段代码中,两个类都会被识别成控制器:
public class PocoController
{public IActionResult Index(){return new ContentResult() { Content = “Hello from POCO controller!” };}
}
[Controller]
public class Poco
{public IActionResult Index(){return new ContentResult() { Content = “Hello from POCO controller!” };}
}
POCO控制器原理
其实,在ASP.NET Core中,已经不像旧版本的 ASP.NET WebApi 那样,通过ControllerFactory来创建Controller,多亏于ASP.NET Core一脉相承的IoC框架 Microsoft.Extensions.DependencyInjection,ASP.NET Core中的内部实现变得更优雅。其中POCO控制器的核心原理就在IApplicationFeatureProvider<ControllerFeature>这个接口的实现ControllerFeatureProvider。
通过aspnet/Mvc项目的Github源码仓库中查询得知,Mvc里把Controller、ViewComponent、TagHelper、Views等组件定义为特性(Feature),如ControllerFeature,特性里就存放了应用中被识别为相组件的类型的集合,如如ControllerFeature中就存放了所有Controller类型。IApplicationFeatureProvider<ControllerFeature>这个接口是用来给MVC框架提供控制器类型识别的接口,当把这个接口的实现注册到服务配置中,就能为其中识别的类型提供控制器功能。
ControllerFeatureProvider是这个接口的默认实现,其中有一个方法IsController(TypeInfo typeInfo)的功能就是判断某类型是否为控制器的。而接口方法PopulateFeature(IEnumerable<ApplicationPart> parts,ControllerFeature feature)则为把传入的 “Mvc应用部分(ApplicationPart,大概是指Mvc的作用程序集)”中的类型都一一判断,如果是控制器,那么就加入控制器特性对象中。
ControllerFeatureProvider
ControllerFeatureProvider
是 ASP.NET Core MVC 框架中的一个类,它实现了 IApplicationFeatureProvider<ControllerFeature>
接口。这个类的主要作用是提供控制器类型的识别功能。
在 ASP.NET Core MVC 中,控制器是用来处理 HTTP 请求的类。传统的控制器类需要继承自 Controller
基类,但 ASP.NET Core 引入了一个新特性,即 POCO(Plain Old CLR Object)控制器。POCO 控制器允许你创建没有继承自 Controller
基类的类,但仍然可以将其识别为控制器,并赋予其处理 HTTP 请求的能力。
ControllerFeatureProvider
就是负责识别这些 POCO 控制器的类。它实现了 IApplicationFeatureProvider<ControllerFeature>
接口的 PopulateFeature
方法,该方法会在 MVC 框架构建应用模型时被调用。在这个方法中,ControllerFeatureProvider
会扫描应用程序中的类型,并根据一定的规则判断哪些类型应该被识别为控制器。
默认情况下,ControllerFeatureProvider
会将所有带有 “Controller” 后缀的类,或者使用了 [Controller]
特性的类识别为控制器。但你也可以通过自定义 ControllerFeatureProvider
的子类来提供自己的识别规则,以满足特定的需求。
https://github.com/dotnet/aspnetcore/blob/main/src/Mvc/Mvc.Core/src/Controllers/ControllerFeatureProvider.cs
实现自定义判断规则
通过上面的剖析,我们就知道要实现自定义的控制器判断规则,只需要重写ControllerFeature类或者重新实现IApplicationFeatureProvider接口,但是由于PopulateFeature不是虚方法或抽象方法,所以不能被重写,那么只能重新写一个类来实现IApplicationFeatureProvider接口了:
public class MyDynamicControllerFeatureProvider : ControllerFeatureProvider{protected override bool IsController(TypeInfo typeInfo){var typeInfo = type.GetTypeInfo();if (!typeof(IDynamicWebApi).IsAssignableFrom(type) ||!typeInfo.IsPublic || typeInfo.IsAbstract || typeInfo.IsGenericType){return false;}var attr = ReflectionHelper.GetSingleAttributeOrDefaultByFullSearch<DynamicWebApiAttribute>(typeInfo);if (attr == null){return false;}if (ReflectionHelper.GetSingleAttributeOrDefaultByFullSearch<NonDynamicWebApiAttribute>(typeInfo) != null){return false;}return true;}}
IApplicationModelConvention
IApplicationModelConvention
是ASP.NET Core中的一个接口,它允许开发者在应用模型构建过程中应用自定义约定。ASP.NET Core 的应用模型是描述如何构建 HTTP 请求处理管道的一组组件和服务。
通过实现 IApplicationModelConvention
接口,开发者可以注册中间件、修改路由、添加模型绑定器、配置控制器和服务等。这些约定在应用的启动过程中被应用,通常在 Startup.ConfigureServices
方法中通过调用 AddApplicationPart
和 ApplyApplicationPartManager
方法来注册。
https://learn.microsoft.com/zh-cn/dotnet/api/microsoft.aspnetcore.mvc.applicationmodels.iapplicationmodelconvention?view=aspnetcore-8.0
下面是一个简单的 IApplicationModelConvention
实现示例,该示例演示了如何为所有控制器添加一个自定义操作筛选器:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ApplicationModels; public class CustomConvention : IApplicationModelConvention
{ public void Apply(ApplicationModel application) { foreach (var controller in application.Controllers) { // 为每个控制器添加自定义操作筛选器 controller.Filters.Add(new CustomActionFilter()); } }
} public class CustomActionFilter : IActionFilter
{ public void OnActionExecuting(ActionExecutingContext context) { // 在操作执行前执行的代码 } public void OnActionExecuted(ActionExecutedContext context) { // 在操作执行后执行的代码 }
}
然后,在 Startup.ConfigureServices
方法中注册这个约定:
public void ConfigureServices(IServiceCollection services)
{ services.AddControllers(); services.AddApplicationPart(typeof(Startup).Assembly) .ApplyApplicationPartManager(manager => { manager.Conventions.Add(new CustomConvention()); });
}
在这个例子中,CustomConvention
被添加到了 ApplicationModel
的约定集合中。每当 ASP.NET Core 构建应用模型时,Apply
方法就会被调用,并且所有的控制器都会被添加 CustomActionFilter
筛选器。
Panda.DynamicWebApi中的实现
ConfigureApiExplorer()
首先,是对ApiExplorer进行配置。通过ApiExplorer,我们可以控制Controller级别和Action级别的Web API的可见性。一般情况下的用法是在Controller或者Action上添加ApiExplorerSettings标记,而在这里,我们只需要给ControllerModel和ActionModel的ApiExplorer属性赋值即可。
private void ConfigureApiExplorer(ControllerModel controller){if (controller.ApiExplorer.GroupName.IsNullOrEmpty()){controller.ApiExplorer.GroupName = controller.ControllerName;}if (controller.ApiExplorer.IsVisible == null){controller.ApiExplorer.IsVisible = true;}foreach (var action in controller.Actions){if (!CheckNoMapMethod(action))ConfigureApiExplorer(action);}}private void ConfigureApiExplorer(ActionModel action){if (action.ApiExplorer.IsVisible == null){action.ApiExplorer.IsVisible = true;}}
ConfigureSelector()
接下来,是对路由进行配置。这部分的核心其实就是根据AreaName、ControllerName、ActionName来生成路由信息,我们会为没有配置过特性路由的Action生成默认的路由,这其实就是MVC里约定大于配置的一种体现啦。在这里会涉及到对ControllerName和ActionName的优化调整,主要体现在两个方面,其一是对类似XXXService、XXXController等这样的后缀进行去除,使其构造出的Api路由更加短小精简;其二是对ActionName里的Get/Save/Update等动词进行替换,使其构造出的Api路由更加符合RESTful风格。
private void ConfigureSelector(ControllerModel controller, DynamicWebApiAttribute controllerAttr){if (controller.Selectors.Any(selector => selector.AttributeRouteModel != null)){return;}var areaName = string.Empty;if (controllerAttr != null){areaName = controllerAttr.Module;}foreach (var action in controller.Actions){if (!CheckNoMapMethod(action))ConfigureSelector(areaName, controller.ControllerName, action);}}private void ConfigureSelector(string areaName, string controllerName, ActionModel action){var nonAttr = ReflectionHelper.GetSingleAttributeOrDefault<NonDynamicWebApiAttribute>(action.ActionMethod);if (nonAttr != null){return;}if (action.Selectors.IsNullOrEmpty() || action.Selectors.Any(a => a.ActionConstraints.IsNullOrEmpty())){if (!CheckNoMapMethod(action))AddAppServiceSelector(areaName, controllerName, action);}else{NormalizeSelectorRoutes(areaName, controllerName, action);}}private void AddAppServiceSelector(string areaName, string controllerName, ActionModel action){var verb = GetHttpVerb(action);action.ActionName = GetRestFulActionName(action.ActionName);var appServiceSelectorModel = action.Selectors[0];if (appServiceSelectorModel.AttributeRouteModel == null){appServiceSelectorModel.AttributeRouteModel = CreateActionRouteModel(areaName, controllerName, action);}if (!appServiceSelectorModel.ActionConstraints.Any()){appServiceSelectorModel.ActionConstraints.Add(new HttpMethodActionConstraint(new[] { verb }));switch (verb){case "GET":appServiceSelectorModel.EndpointMetadata.Add(new HttpGetAttribute());break;case "POST":appServiceSelectorModel.EndpointMetadata.Add(new HttpPostAttribute());break;case "PUT":appServiceSelectorModel.EndpointMetadata.Add(new HttpPutAttribute());break;case "DELETE":appServiceSelectorModel.EndpointMetadata.Add(new HttpDeleteAttribute());break;default:throw new Exception($"Unsupported http verb: {verb}.");}}}
ConfigureParameters()
接下来参数绑定相对简单,因为简单类型MVC自己就能完成绑定,所以,我们只需要关注复杂类型的绑定即可,最常见的一种绑定方式是FromBody:
private void ConfigureParameters(ControllerModel controller){foreach (var action in controller.Actions){if (!CheckNoMapMethod(action))foreach (var para in action.Parameters){if (para.BindingInfo != null){continue;}if (!TypeHelper.IsPrimitiveExtendedIncludingNullable(para.ParameterInfo.ParameterType)){if (CanUseFormBodyBinding(action, para)){para.BindingInfo = BindingInfo.GetBindingInfo(new[] { new FromBodyAttribute() });}}}}}
相关文章:

Asp .Net Core 系列:Asp .Net Core 集成 Panda.DynamicWebApi
文章目录 简介Asp .Net Core 集成 Panda.DynamicWebApi配置原理什么是POCO Controller?POCO控制器原理ControllerFeatureProvider实现自定义判断规则IApplicationModelConventionPanda.DynamicWebApi中的实现ConfigureApiExplorer()ConfigureSelector()ConfigurePar…...

【PTA浙大版《C语言程序设计(第4版)》|编程题】习题7-3 判断上三角矩阵(附测试点)
目录 输入格式: 输出格式: 输入样例: 输出样例: 代码呈现 测试点 上三角矩阵指主对角线以下的元素都为0的矩阵;主对角线为从矩阵的左上角至右下角的连线。 本题要求编写程序,判断一个给定的方阵是否…...

JVM 性能调优 - 参数调优(3)
查看 JVM 内存的占用情况 编写代码 package com.test;public class PrintMemoryDemo {public static void main(String[] args) {// 堆内存总量long totalMemory Runtime.getRuntime().totalMemory();// jvm 试图使用的最大堆内存long maxMemory Runtime.getRuntime().maxM…...

Django(十)
1. Ajax请求 浏览器向网站发送请求时:URL 和 表单的形式提交。 GETPOST 特点:页面刷新。 除此之外,也可以基于Ajax向后台发送请求(偷偷的发送请求)。 依赖jQuery编写ajax代码 $.ajax({url:"发送的地址"…...
OpenHarmony开源鸿蒙开发之旅
文章目录 一、op系统架构二、op系统构建1. op源代码目录2. op系统构建3. op开发环境搭建 三、op系统的子系统四、op系统芯片移植五、op系统启动流程六、op系统组件七、驱动框架 一、op系统架构 二、op系统构建 1. op源代码目录 2. op系统构建 3. op开发环境搭建 三、op系统…...
SpringBoot之整合PageHelper分页插件
SpringBoot之整合PageHelper分页插件 文章目录 SpringBoot之整合PageHelper分页插件1. 引入坐标2. application.yml配置3. 基本使用4. 对多个查询执行分页1. 默认第一个Select语句会执行分页2. 让Pagehelper也能执行多个分页的方法3. 完整案例 详细配置请查看官网或MyBatis分页…...
Android java基础_类的封装
一.面向对象编程的引入 写一个简单的程序输出张三,李四的名字 class Person {String name;String getName() {return "guangdong "name;} };public class Oop {public static void main(String args[]) {Person p1 new Person();p1.name "zhangs…...
Vue-57、Vue技术路由的参数如何传递
query参数传递 1、传递参数 <!-- 跳转路由并携带query参数,to的字符串写法--> <router-link :to"/home/message/detail?id${p.id}&title${p.title}"> {{p.title}} </router-link><!-- 跳转路由…...

《MySQL 简易速速上手小册》第1章:MySQL 基础和安装(2024 最新版)
文章目录 1.1 MySQL 概览:版本、特性和生态系统1.1.1 基础知识1.1.2 重点案例:使用 Python 实现 MySQL 数据的 CRUD 操作1.1.3 拓展案例 1:使用 Python 实现 MySQL 数据备份**1.1.4 拓展案例 2:使用 Python 分析 MySQL 数据 1.2 安…...

Linux 软件管理(YUM RPM)
1 YUM yum(全称为 Yellow dog Updater, Modified)是一个在Fedora和RedHat以及CentOS中的Shell前端软件包管理器。基于RPM包管理,能够从指定的服务器自动处理依赖性关系,并且一次安装所有依赖的软件包,无须繁琐地一次次…...
【Makefile语法 05】动静态库编译链接
目录 一、多文件项目源代码 二、静态库编译链接 三、动态库编译链接 一、多文件项目源代码 // include/add.hpp#pragma once int add(int a, int b); // include/sub.hpp#pragma once int sub(int a, int b); // src/add.cpp#include "add.hpp"int add(int a, …...
JS - 处理元素滚动
业务功能中时常有元素滚动的功能,现在就总结一下一些常用的事件。 一、定位滚动元素 做一切滚动操作之前都应该先定位到滚动元素,再做其他操作,如滚动顶部,获取滚动距离、禁止滚动等。 把以下代码复制粘贴到浏览器 Console 面板…...

JavaScript滚动事件
🧑🎓 个人主页:《爱蹦跶的大A阿》 🔥当前正在更新专栏:《VUE》 、《JavaScript保姆级教程》、《krpano》、《krpano中文文档》 ✨ 前言 滚动是网页交互不可或缺的一部分。监听页面和元素的滚动事件,可以帮助…...

4.0 Zookeeper Java 客户端搭建
本教程使用的 IDE 为 IntelliJ IDEA,创建一个 maven 工程,命名为 zookeeper-demo,并且引入如下依赖,可以自行在maven中央仓库选择合适的版本,介绍原生 API 和 Curator 两种方式。 IntelliJ IDEA 相关介绍:…...
C#既然数组长度不可改变,那么如何动态调整集合类型数组大小,以便添加或删除元素?
目录 1.使用动态数组(ArrayList): 2.使用 jagged array(不规则数组): 3.使用 List : 4.使用数组复制: 在C#中,数组的长度是固定的,一旦声明和初始化&…...

3.1 Verilog 连续赋值
关键词:assign, 全加器 连续赋值语句是 Verilog 数据流建模的基本语句,用于对 wire 型变量进行赋值。: 格式如下 assign LHS_target RHS_expression ; LHS(left hand side) 指赋值操作…...

【http】2、http request header Origin 属性、跨域 CORS、同源、nginx 反向代理、预检请求
文章目录 一、Origin 含义二、跨源资源共享:**Cross-Origin Resource Sharing** CORS2.1 跨域的定义2.2 功能概述2.3 场景示例2.3.1 简单请求2.3.2 Preflighted requests:预检请求 2.4 header2.4.1 http request header2.4.1.1 Origin2.4.1.2 Access-Con…...
LangChain pdf的读取以及向量数据库的使用
以下使用了3399.pdf, Rockchip RK3399 TRM Part1 import ChatGLM from langchain.chains import LLMChain from langchain_core.output_parsers import StrOutputParser from langchain_core.prompts import ChatPromptTemplate from langchain.chains import Simp…...
VUE学习——事件修饰符
阻止默认事件 <template><a click"onClickHandle" href"https://www.baidu.com">baidu</a><a click.prevent"onClickHandle" href"https://www.baidu.com">baidu</a> </template> <script>…...

开放平台技术架构设计与实现的实战总结
开放平台是企业向外部开发者提供API接口和服务的平台,促进生态系统的建设和业务拓展。本文将介绍开放平台技术架构的设计原则和实现方法,帮助读者了解如何构建一个稳健、安全且易于扩展的开放平台。 1. 什么是开放平台? - 解释了开放平台…...
rknn优化教程(二)
文章目录 1. 前述2. 三方库的封装2.1 xrepo中的库2.2 xrepo之外的库2.2.1 opencv2.2.2 rknnrt2.2.3 spdlog 3. rknn_engine库 1. 前述 OK,开始写第二篇的内容了。这篇博客主要能写一下: 如何给一些三方库按照xmake方式进行封装,供调用如何按…...
Python爬虫实战:研究feedparser库相关技术
1. 引言 1.1 研究背景与意义 在当今信息爆炸的时代,互联网上存在着海量的信息资源。RSS(Really Simple Syndication)作为一种标准化的信息聚合技术,被广泛用于网站内容的发布和订阅。通过 RSS,用户可以方便地获取网站更新的内容,而无需频繁访问各个网站。 然而,互联网…...

全志A40i android7.1 调试信息打印串口由uart0改为uart3
一,概述 1. 目的 将调试信息打印串口由uart0改为uart3。 2. 版本信息 Uboot版本:2014.07; Kernel版本:Linux-3.10; 二,Uboot 1. sys_config.fex改动 使能uart3(TX:PH00 RX:PH01),并让boo…...
蓝桥杯 冶炼金属
原题目链接 🔧 冶炼金属转换率推测题解 📜 原题描述 小蓝有一个神奇的炉子用于将普通金属 O O O 冶炼成为一种特殊金属 X X X。这个炉子有一个属性叫转换率 V V V,是一个正整数,表示每 V V V 个普通金属 O O O 可以冶炼出 …...
MySQL JOIN 表过多的优化思路
当 MySQL 查询涉及大量表 JOIN 时,性能会显著下降。以下是优化思路和简易实现方法: 一、核心优化思路 减少 JOIN 数量 数据冗余:添加必要的冗余字段(如订单表直接存储用户名)合并表:将频繁关联的小表合并成…...

在 Spring Boot 中使用 JSP
jsp? 好多年没用了。重新整一下 还费了点时间,记录一下。 项目结构: pom: <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://ww…...

uniapp 小程序 学习(一)
利用Hbuilder 创建项目 运行到内置浏览器看效果 下载微信小程序 安装到Hbuilder 下载地址 :开发者工具默认安装 设置服务端口号 在Hbuilder中设置微信小程序 配置 找到运行设置,将微信开发者工具放入到Hbuilder中, 打开后出现 如下 bug 解…...

什么是VR全景技术
VR全景技术,全称为虚拟现实全景技术,是通过计算机图像模拟生成三维空间中的虚拟世界,使用户能够在该虚拟世界中进行全方位、无死角的观察和交互的技术。VR全景技术模拟人在真实空间中的视觉体验,结合图文、3D、音视频等多媒体元素…...
HTML前端开发:JavaScript 获取元素方法详解
作为前端开发者,高效获取 DOM 元素是必备技能。以下是 JS 中核心的获取元素方法,分为两大系列: 一、getElementBy... 系列 传统方法,直接通过 DOM 接口访问,返回动态集合(元素变化会实时更新)。…...
十九、【用户管理与权限 - 篇一】后端基础:用户列表与角色模型的初步构建
【用户管理与权限 - 篇一】后端基础:用户列表与角色模型的初步构建 前言准备工作第一部分:回顾 Django 内置的 `User` 模型第二部分:设计并创建 `Role` 和 `UserProfile` 模型第三部分:创建 Serializers第四部分:创建 ViewSets第五部分:注册 API 路由第六部分:后端初步测…...