CORS跨域资源共享(二):详解Spring MVC对CORS支持的相关类和API【享学Spring MVC】
每篇一句
重构一时爽,一直重构一直爽。但出了问题火葬场
前言
上篇文章通过我模拟的跨域请求实例和结果分析,相信小伙伴们都已经80%
的掌握了CORS
到底是怎么一回事以及如何使用它。由于Java语言中的web框架几乎都是使用的Spring MVC
,因此本文将聚焦于Spring MVC
对CORS
的支持,深度分析下它对CORS支持的相关API,这也方便下一章节的灵活使用以及流程原理分析。
Spring MVC与CORS
Spring MVC
一直到4.2
版本“才”开始内置对CORS
支持,至于为何到这个版本Spring
官方才对此提供支持,我这里需要结合时间轴来给大家解释一下。
上文我有说到了CORS
它属于W3C
的标准。我们知道任何一个规范的形成都是非常漫长的。W3C
对web标准的制定分为如下7个阶段(从上到下有序):
- WD(Working Draft 工作草案):不稳定也不完整
- CR(Candidate Recommendation 候选推荐标准):所有的已知
issues
都被解决了 - PR(Proposed Recommendation 提案推荐标准):在浏览器做各种测试,此部分不会再有实质性的改动
- PER(Proposed Edited Recommendation 已修订的提案推荐标准):
REC
(Recommendation 推荐标准,通常称之为 standard,即事实标准):几乎不会再变动任何东西- RET(Retired 退役的):最后这两个是建立在
REC
基础上变来,成熟的技术一般都不会有后面这两个 - NOTE(Group Note 工作组说明):
关于这7步,从这里 可以看倒CORS
的WD从2009-03-17
开始,2014-01-16
进入的REC
阶段,可谓正式毕业。而Spring4.2
是在2015-06
发布给与的全面支持,从时间轴上看Spring
的响应速度还是把握得不错的(毕竟CORS
经历过一段时间市场的考验Spring
才敢全面纳入进来支持嘛~)
Tips:在
Spring4.2
之前,官方没有提供内置的支持,所以那时都是自己使用Filter/拦截器来处理。它的唯一缺点就是可能没那么灵活和优雅,后续官方提供标注支持后能力更强更为灵活了(底层原理都一样)
Spring MVC中CORS相关类及API说明
所有涉及到和CORS
相关的类、注解、代码片段都是Spring4.2
后才有的,请保持一定的版本意识。
从截图里可以看出spring-web
包提供的专门用于处理CORS
的相关的类,下面有必要进行逐个分析
CorsConfiguration
它代表一个cors
配置,记录着各种配置项。它还提供了检查给定请求的实际来源、http方法和头的方法供以调用。用人话说:它就是具体封装跨域配置信息的pojo。
默认情况下新创建的CorsConfiguration
它是不允许任何跨域请求的,需要你手动去配置,或者调用applyPermitDefaultValues()
开启GET、POST、Head
的支持~
几乎所有场景,创建完
CorsConfiguration
最后都调用了applyPermitDefaultValues()
方法。也就是说你不干预的情况下,一个CorsConfiguration
配置一般都是支持GET、POST、Head
的
// @since 4.2
public class CorsConfiguration {// public的通配符:代表所有的源、方法、headers...// 若你需要使用通配符,可以使用此静态常量public static final String ALL = "*";private static final List<HttpMethod> DEFAULT_METHODS = Collections.unmodifiableList(Arrays.asList(HttpMethod.GET, HttpMethod.HEAD));// 默认许可所有方法private static final List<String> DEFAULT_PERMIT_ALL = Collections.unmodifiableList(Arrays.asList(ALL));// 默认许可这三个方法private static final List<String> DEFAULT_PERMIT_METHODS = Collections.unmodifiableList(Arrays.asList(HttpMethod.GET.name(), HttpMethod.HEAD.name(), HttpMethod.POST.name()));// ==========把这些属性对应上文讲述的响应头们对应,和W3C标注都是对应上的=========@Nullableprivate List<String> allowedOrigins;@Nullableprivate List<String> allowedMethods;@Nullableprivate List<HttpMethod> resolvedMethods = DEFAULT_METHODS;@Nullableprivate List<String> allowedHeaders;@Nullableprivate List<String> allowedHeaders;@Nullableprivate List<String> exposedHeaders;@Nullableprivate Boolean allowCredentials;@Nullableprivate Long maxAge;... // 省略所有构造函数以及所有的get/set方法// 使用此方法将初始化模型翻转为以允许get、head和post请求的所有跨源请求的打开默认值开始// 注意:此方法不会覆盖前面set进去的值,所以建议此方法可以作为兜底调用。实际上Spring内部也是用它兜底的public CorsConfiguration applyPermitDefaultValues() {if (this.allowedOrigins == null) {this.allowedOrigins = DEFAULT_PERMIT_ALL;}if (this.allowedMethods == null) {this.allowedMethods = DEFAULT_PERMIT_METHODS;this.resolvedMethods = DEFAULT_PERMIT_METHODS.stream().map(HttpMethod::resolve).collect(Collectors.toList());}if (this.allowedHeaders == null) {this.allowedHeaders = DEFAULT_PERMIT_ALL;}if (this.maxAge == null) {this.maxAge = 1800L;}return this;}public CorsConfiguration combine(@Nullable CorsConfiguration other) { ... }// 根据配置的允许来源检查请求的来源// 返回值并不是bool值,而是字符串--> 返回可用的origin。若是null表示请求的origin不被支持@Nullablepublic String checkOrigin(@Nullable String requestOrigin) { ... }// 检查预检请求的Access-Control-Request-Method这个请求头public List<HttpMethod> checkHttpMethod(@Nullable HttpMethod requestMethod) { ... }// 检查预检请求的Access-Control-Request-Headers@Nullablepublic List<String> checkHeaders(@Nullable List<String> requestHeaders) {}
这个POJO的配置,是servlet
传统web以及reactive web
所共用的,它提供有校验的基本方法。它的属性、校验原则和W3C的CORS标准所对应。
CorsConfigurationSource
它表示一个源,该接口主要是为请求提供一个CorsConfiguration
。
public interface CorsConfigurationSource {// 找到此request的一个CORS配置@NullableCorsConfiguration getCorsConfiguration(HttpServletRequest request);
}
此接口方法的调用处有三个地方:
- AbstractHandlerMapping.getHandler()/getCorsConfiguration()
- CorsFilter.doFilterInternal()
- HandlerMappingIntrospector.getCorsConfiguration()
因为它可以根据request
返回一个CORS配置。可以把这个接口理解为:存储request与跨域配置信息的容器。它的继承树如下:
首先需要说的便是cors包下的UrlBasedCorsConfigurationSource
UrlBasedCorsConfigurationSource
它位于org.springframework.web.cors
包:它里面存储着path patterns
和CorsConfiguration
的键值对。
// @since 4.2
public class UrlBasedCorsConfigurationSource implements CorsConfigurationSource {// 请务必注意:这里使用的是LinkedHashMapprivate final Map<String, CorsConfiguration> corsConfigurations = new LinkedHashMap<>();private PathMatcher pathMatcher = new AntPathMatcher();private UrlPathHelper urlPathHelper = new UrlPathHelper();... // 生路所有的get/set方法// 这里的path匹配用到的是AntPathMatcher.match(),默认是按照ant风格进行匹配的@Override@Nullablepublic CorsConfiguration getCorsConfiguration(HttpServletRequest request) {String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);for (Map.Entry<String, CorsConfiguration> entry : this.corsConfigurations.entrySet()) {if (this.pathMatcher.match(entry.getKey(), lookupPath)) {return entry.getValue();}}return null;}
}
本类它是作为AbstractHandlerMapping
(RequestMappingHandlerMapping
)的默认跨域资源配置的管理类
HandlerMappingIntrospector
HandlerMapping
内省器。它是一个帮助类用于从HandlerMapping
里获取信息,这些信息用于服务特定的请求。@EnableWebMvc
默认会把它放进容器里,开发者可以@Autowired
拿来使用(框架内部木有使用)
这个类比较重要,
Spring Cloud Netflix Zuul
巧用它实现了一些功能~
// @since 4.3.1
public class HandlerMappingIntrospector implements CorsConfigurationSource, ApplicationContextAware, InitializingBean {@Nullableprivate ApplicationContext applicationContext;@Nullableprivate List<HandlerMapping> handlerMappings;... // 生路一些构造函数、set方法// 1、Map<String, HandlerMapping> beans = BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, HandlerMapping.class, true, false)// 2、如果第一步获取到了Beans,sort()排序一下// 3、若没找到,回退到`DispatcherServlet.properties`这个配置文件里去找@Overridepublic void afterPropertiesSet() {if (this.handlerMappings == null) {Assert.notNull(this.applicationContext, "No ApplicationContext");this.handlerMappings = initHandlerMappings(this.applicationContext);}}// 从这些HandlerMapping找到MatchableHandlerMapping// 若一个都木有,此方法抛出异常@Nullablepublic MatchableHandlerMapping getMatchableHandlerMapping(HttpServletRequest request) throws Exception { ... }@Override@Nullablepublic CorsConfiguration getCorsConfiguration(HttpServletRequest request) {Assert.notNull(this.handlerMappings, "Handler mappings not initialized");HttpServletRequest wrapper = new RequestAttributeChangeIgnoringWrapper(request);for (HandlerMapping handlerMapping : this.handlerMappings) {HandlerExecutionChain handler = null;try {handler = handlerMapping.getHandler(wrapper);} catch (Exception ex) {// Ignore}if (handler == null) {continue;}// 拿到作用在此Handler上的所有的拦截器们:HandlerInterceptor// 若有拦截器实现了CorsConfigurationSource接口,那就返回此拦截器上的CORS配置源if (handler.getInterceptors() != null) {for (HandlerInterceptor interceptor : handler.getInterceptors()) {if (interceptor instanceof CorsConfigurationSource) {return ((CorsConfigurationSource) interceptor).getCorsConfiguration(wrapper);}}}// 若这个Handle本身(注意:并不是所有的handler都是一个方法,也可能是个类,所以也有可能是会实现接口的)// 就是个CorsConfigurationSource 那就以它的为准if (handler.getHandler() instanceof CorsConfigurationSource) {return ((CorsConfigurationSource) handler.getHandler()).getCorsConfiguration(wrapper);}}return null;}
}
这个自省器最重要的功能就是初始化的时候把所有的HandlerMapping
都拿到了。这个处理逻辑和DispatcherServlet.initHandlerMappings
是一样的,为何不提取成公用的呢???
它另外一个功能便是获取HttpServletRequest
对应的CORS
配置信息:
- 从作用在此
Handler
的拦截器HandlerInterceptor
上获取 - 若拦截器里木有,那就从
Handler
本身获取(若实现了CorsConfigurationSource
接口) - 都没有就返回null
CorsInterceptor
Cors
拦截器。它最终会被放到处理器链HandlerExecutionChain
里,用于拦截处理(最后一个拦截)。
private class CorsInterceptor extends HandlerInterceptorAdapter implements CorsConfigurationSource {@Nullableprivate final CorsConfiguration config;//拦截操作 最终是委托给了`CorsProcessor`,也就是DefaultCorsProcessor去完成处理的@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {return corsProcessor.processRequest(this.config, request, response);}@Override@Nullablepublic CorsConfiguration getCorsConfiguration(HttpServletRequest request) {return this.config;}}
该拦截器是AbstractHandlerMapping
的私有内部类,它会在每次getHandler()
的时候放进去专门作用域当前跨域的请求,具体的流程在下个章节里有讲述。
PreFlightHandler
这个和上面的CorsInterceptor
互斥,它最终也是委托给corsProcessor
来处理请求,只是它是专门用于处理预检请求的。详见CORS
请求处理流程部分。
CorsProcessor(重要)
它便是CORS
真正处理器:用于接收请求和一个配置,然后更新Response:比如接受/拒绝
public interface CorsProcessor {// 根据所给的`CorsConfiguration`来处理请求boolean processRequest(@Nullable CorsConfiguration configuration, HttpServletRequest request, HttpServletResponse response) throws IOException;
}
它的唯一实现类是DefaultCorsProcessor
DefaultCorsProcessor
它遵循的是W3C标准实现的。Spring MVC
中对CORS
规则的校验,都是通过委托给 DefaultCorsProcessor
实现的
// @since 4.2
public class DefaultCorsProcessor implements CorsProcessor {@Override@SuppressWarnings("resource")public boolean processRequest(@Nullable CorsConfiguration config, HttpServletRequest request, HttpServletResponse response) throws IOException {// 若不是跨域请求,不处理// 这个判断极其简单:请求中是否有Origin请求头。有这个头就是跨域请求if (!CorsUtils.isCorsRequest(request)) {return true;}// response.getHeaders().getAccessControlAllowOrigin() != null// 若响应头里已经设置好了Access-Control-Allow-Origin这个响应头,此处理器也不管了ServletServerHttpResponse serverResponse = new ServletServerHttpResponse(response);if (responseHasCors(serverResponse)) {logger.trace("Skip: response already contains \"Access-Control-Allow-Origin\"");return true;}// 即使你有Origin请求头,但是是同源的请求,那也不处理ServletServerHttpRequest serverRequest = new ServletServerHttpRequest(request);if (WebUtils.isSameOrigin(serverRequest)) {logger.trace("Skip: request is from same origin");return true;}// 是否是预检请求,判断标准如下:// 是跨域请求 && 是`OPTIONS`请求 && 有Access-Control-Request-Method这个请求头boolean preFlightRequest = CorsUtils.isPreFlightRequest(request);// 若config == null,分两种case:// 是预检请求but木有给config,那就拒绝:给出状态码403// response.setStatusCode(HttpStatus.FORBIDDEN)// response.getBody().write("Invalid CORS request".getBytes(StandardCharsets.UTF_8));if (config == null) {if (preFlightRequest) {rejectRequest(serverResponse);return false; // 告诉后面的处理器不用再处理了} else { // 虽然没给config,但不是预检请求(是真是请求,返回true)return true;}}// 真正的跨域处理逻辑~~~~// 它的处理逻辑比较简单,立即了W3C规范理解它起来非常简单,本文略// checkOrigin/checkMethods/checkHeaders等等方法最终都是委托给CorsConfiguration去做的return handleInternal(serverRequest, serverResponse, config, preFlightRequest);}...
}
使用框架来处理跨域的好处便是:兼容性很强且灵活。它的处理过程如下:
- 若不是跨域请求,不处理(注意是
return true
后面拦截器还得执行呢)。若是跨域请求继续处理。(是否是跨域请求就看请求头是否有Origin
这个头) - 判断
response
是否有Access-Control-Allow-Origin
这个响应头,若有说明已经被处理过,那本处理器就不再处理了 - 判断是否是同源:即使有Origin请求头,但若是同源的也不处理
- 是否配置了
CORS
规则,若没有配置:
1. 若是预检请求,直接决绝403,return false
2. 若不是预检请求,则本处理器不处理 - 正常处理
CORS
请求,大致是如下步骤:
1. 判断 origin 是否合法
2. 判断 method 是否合法
3. 判断 header是否合法
4. 若其中有一项不合法,直接决绝掉403并return false
。都合法的话:就在response
设置上一些头信息~~~
CorsFilter
Spring4.2
之前一般自己去实现一个这样的Filter
来处理,4.2
之后框架提供了内置支持。
Reactive的叫
org.springframework.web.cors.reactive.CorsWebFilter
// @since 4.2
public class CorsFilter extends OncePerRequestFilter {private final CorsConfigurationSource configSource;// 默认使用的DefaultCorsProcessor,当然你也可以自己指定private CorsProcessor processor = new DefaultCorsProcessor();@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {// 只处理跨域请求if (CorsUtils.isCorsRequest(request)) {// Spring这里有个bug:因为它并不能保证configSource肯定被初始化了CorsConfiguration corsConfiguration = this.configSource.getCorsConfiguration(request);if (corsConfiguration != null) {boolean isValid = this.processor.processRequest(corsConfiguration, request, response);// 若处理后返回false,或者该请求本身就是个Options请求,那后面的Filter也不要处理了~~~~~if (!isValid || CorsUtils.isPreFlightRequest(request)) {return;}}}filterChain.doFilter(request, response);}
}
它的工作完全委托给CorsProcessor
去处理的。此Filter
可以与DelegatingFilterProxy
一起使用,以帮助初始化且可以使用Spring容器内的Bean。注意CorsFilter
在框架内部默认是木有配置的,若有需要请自行配置~
CorsFilter
属于jar包内的过滤器,在没有web.xml环境下如何配置呢?详见下个章节的示例
@CrossOrigin
Spring MVC
提供了此注解来帮助你解决CORS跨域问题,比你使用Filter
更加的方便,且能实现更加精细化的控制(一般可以和CorsFilter
一起来使用,效果更佳)。
Spring Web MVC
和Spring WebFlux
在RequestMappingHandlerMapping
里都是支持此注解的,该注解配置参数的原理可参考CorsConfiguration
// @since 4.2 可使用在类上和方法上
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CrossOrigin {// 下面4个属性在5.0后都被此方法所代替 因为此方法默认会被执行/** @deprecated as of Spring 5.0, in favor of {@link CorsConfiguration#applyPermitDefaultValues} */@DeprecatedString[] DEFAULT_ORIGINS = { "*" };@DeprecatedString[] DEFAULT_ALLOWED_HEADERS = { "*" };@Deprecatedboolean DEFAULT_ALLOW_CREDENTIALS = false;@Deprecatedlong DEFAULT_MAX_AGE = 1800;// 若需要通配,可写*@AliasFor("origins")String[] value() default {};@AliasFor("value")String[] origins() default {};String[] allowedHeaders() default {};String[] exposedHeaders() default {};RequestMethod[] methods() default {};String allowCredentials() default "";long maxAge() default -1; // 负值意味着不生效 By default this is set to {@code 1800} seconds (30 minutes)
}
此注解可以标注在Controller
上和方法上,若都有标注将会有combine的效果。
CorsRegistry / CorsRegistration
这两个类是Spring MVC
提供出来便于进行global全局配偶的,它是基于URL pattern
配置的。
public class CorsRegistry {// 保存着全局的配置,每个CorsRegistration就是URL pattern和CorsConfiguration配置private final List<CorsRegistration> registrations = new ArrayList<>();// 像上面List添加一个全局配置(和pathPattern绑定)// 它使用的是new CorsRegistration(pathPattern)// 可见使用配置是默认配置:new CorsConfiguration().applyPermitDefaultValues()// 当然它CorsRegistration return给你了,你还可以改(配置)的~~~~public CorsRegistration addMapping(String pathPattern) {CorsRegistration registration = new CorsRegistration(pathPattern);this.registrations.add(registration);return registration;}// 这个就比较简单了:把当前List专程Map。key就是PathPattern~~~~protected Map<String, CorsConfiguration> getCorsConfigurations() {Map<String, CorsConfiguration> configs = new LinkedHashMap<>(this.registrations.size());for (CorsRegistration registration : this.registrations) {configs.put(registration.getPathPattern(), registration.getCorsConfiguration());}return configs;}
}
对于CorsRegistration
这个类,它就是持有pathPattern
和CorsConfiguration config
两个属性,它特特点是提供了allowedMethods/allowedHeaders...
等方法,提供钩子方便我们对CorsConfiguration
进行配置,源码很简单略。
这两个类虽然简单,但是在
@EnableWebMvc
里扩展配置时使用得较多,参见下个章节对WebMvcConfigurer
扩展使用和配置
相关阅读
CORS跨域资源共享(一):模拟跨域请求以及结果分析,理解同源策略【享学Spring MVC】
CORS跨域资源共享(二):详解Spring MVC对CORS支持的相关类和API【享学Spring MVC】
CORS跨域资源共享(三):@CrossOrigin/CorsFilter处理跨域请求示例,原理分析【享学Spring MVC】
总结
本文内容主要介绍Spring MVC
它对CORS
支持的那些类,为我们生产是灵活的使用Spring MVC
解决CORS
问题提供理论基础。下个章节也是本系列的最后一个章节,将具体介绍Spring MVC
中对CORS
的实践。
关注A哥
Author | A哥(YourBatman) |
---|---|
个人站点 | www.yourbatman.cn |
yourbatman@qq.com | |
微 信 | fsx641385712 |
活跃平台
|
|
公众号 | BAT的乌托邦(ID:BAT-utopia) |
知识星球 | BAT的乌托邦 |
每日文章推荐 | 每日文章推荐 |
CORS跨域资源共享(二):详解Spring MVC对CORS支持的相关类和API【享学Spring MVC】相关推荐
- Cors跨域资源请求详解
介绍 Cors全称为"跨域资源共享"(Cross-origin resource sharing),是一个W3C标准,一种浏览器机制,可实现对位于应用程序域之外的资源的受控访问,对 ...
- 解决跨域问题(详解9种方法)
同源策略:端口号.协议.域名相同 . 一.为什么会出现跨域问题 出于浏览器的同源策略限制.同源策略(Sameoriginpolicy)是一种约定,它是浏览器最核心也最基本的安全功能,如 ...
- tomcat7.0配置CORS(跨域资源共享)
平时我们做前台页面时可能会遇到浏览器以下提示(浏览器控制台): 已阻止跨源请求:同源策略禁止读取位于 http://xxx.xxx.com 的远程资源.(原因:CORS 头缺少 'Access-Con ...
- python跨域攻击教学_关于python 跨域处理方式详解
因为浏览器的同源策略限制,不是同源的脚本不能操作其他源下面的资源,想操作另一个源下面的资源就属于跨域了,这里说的跨域是广义跨域,我们常说的代码中请求跨域,是狭义的跨域,即在脚本代码中向非同源域发送ht ...
- cors跨域资源共享】同源策略和jsonp
在执行下面那段代码的时候,我遇到了一个跨域资源共享的问题 <!doctype html> <html> <head> <meta charset=" ...
- CORS跨域资源共享漏洞
目录 同源策略 解决跨域问题的方案 CORS CORS原理 CORS漏洞攻击流程 CORS漏洞案例演示 CORS漏洞挖掘思考 漏洞危害 漏洞修复 跨域资源共享 (CORS) 是一种浏览器机制,可以对位 ...
- Spring Boot CORS跨域资源共享实现方案
同源策略 同源策略(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能 同源策略限制cookie 等信息的跨源网页读取,可以保护本地用户信息 同源策略限制跨域 aja ...
- javascript --- XMLHttp2级、CORS(跨域资源共享)
FormData: // 为序列化表单以及创建与表单格式相同的数据提供了便利 var data = new FromData(); data.append("name", &quo ...
- 前端跨域的理解和解决跨域的方案详解(全)
作为前端开发,我们遇到最多的应该就是跨域问题,对于萌新来说,跨域就是一道墙,不知所措,其实只要理解了跨域的含义和原理,解决它是不难的,今天给大家介绍下什么是跨域和跨域的解决方案! 什么是跨域? 跨域是 ...
最新文章
- AI 架构师 Yoshua Bengio:深度学习的研究,对于工业应用来说太过简单
- JAVA二分查找-探讨思维与代码的一致性
- android设置系统横屏方案
- c语言口令验证模块加强版,[C语言学习第3章口令验证模块的开发.ppt
- Archive for required library: ‘WebContent/WEB-INF/lib/xxx.jar cannotn
- 用宏定义写出swap(x,y)
- libvlc media player in C# (part 1)
- 年总结(八):关于思考的重新认识
- 如何成为一名游戏设计师
- List常用方法总结
- mac用navicat连接mysql_Mac OS下,使用Navicat连接MySQL出现的问题
- 2021年开始,Adobe Flash Player 不能用了?
- 取次花丛懒回顾,半缘修道半缘君
- 【MyBatis】 MyBatis与MyBatis-Plus的区别
- SpringBoot整合activeMQ消息队列手动签收(Session.CLIENT_ACKNOWLEDGE)为什么失效啊?
- 快速排序--QuickSort()--递归版本
- abaqus python_abaqus python脚本入门
- 手机ZTE中兴U802 U807手机解锁图案忘了 如何处理
- qcc514x-qcc304x调试笔记
- 算法13_10种海量数据处理方法
热门文章
- [Swift]LeetCode293. 翻转游戏 $ Flip Game
- 看了这篇,你也是Python文件操作高手
- 学计算机不会重装系统正常吗,电脑那点事 篇一:不会重装windows操作系统?6 分钟就能学会!...
- dnsmasq( DNS和DHCP)服务
- Excel常用的单元格格式
- 全屏时程序坞自动隐藏的方法
- 前端每日实战:97# 视频演示如何用纯 CSS 创作一组昂首阔步的圆点
- 计算机视觉与图形学-神经渲染专题-StructNeRF室内重建
- 77.组合 回溯 队列 剪枝 python
- leetcode 77. Combinations-排列|递归|非递归|Java|Python