Geoserver源码解读四 REST服务

文章目录

文章目录

一、概要

二、前置知识点-FreeMarker

三、前置知识点-AbstractHttpMessageConverter

3.1 描述

3.2 应用

四、前置知识点-AbstractDecorator

4.1描述

4.2 应用

五、工作空间查询解读

5.1 模板解读

5.2 请求转换器解读


一、概要

关于geoserver的rest服务,其实官网有一个简单的描述,此处不多搬运详情可以查看它官网描述(点我),但是需要重点了解的是最新的GeoServer是使用SpringMVC来实现的REST服务,抛弃了Restlet。GeoServer扩展之REST_geoserver过时了-CSDN博客 从GeoServer2.12版(2017)开始采用的SpringMVC, 它的Wiki中也做了个简单描述,但是开发文档没有更新,重要的事情说两遍开发文档没有更新。所以官网描述看看就可以了,不用跟着它的指引做。 本文着重从源码角度梳理整个rest服务的流程

二、前置知识点-FreeMarker

在上一篇文章中看到geoserver的模板框架是FreeMarker

主体框架spring(不是spring boot)
UI框架Wicket(类似jsp)
通信框架(前后台交互)Servlet
地理处理框架GeoTools
模板框架FreeMarker

这个东西主要就是用于格式化REST接口

用法的话参照下面的代码(AI生成的,可能细节上有问题,看看即可)

1.环境配置

Configuration cfg = new Configuration(Configuration.VERSION_2_3_31);
cfg.setDirectoryForTemplateLoading(new File("templates")); // 设置模板目录
cfg.setDefaultEncoding("UTF-8"); // 设置默认编码

2.加载模板

Template template = cfg.getTemplate("example.ftl");

3.数据模型

Map<String, Object> dataModel = new HashMap<>();
dataModel.put("title", "FreeMarker 示例");
dataModel.put("message", "这是一个 FreeMarker 模板!");

4.处理模板

StringWriter out = new StringWriter();
template.process(dataModel, out);
String result = out.toString();
System.out.println(result);

三、前置知识点-AbstractHttpMessageConverter

AbstractHttpMessageConverter 一般与rest 接口联合使用,用于根据前端需求返回不同格式的结果,就比如工作空间的三种请求方式

3.1 描述

下面是AI(智普清言)生成的,可能细节上有问题,看看即可

AbstractHttpMessageConverter 是 Spring 框架中用于处理 HTTP 请求和响应的转换的一个抽象类。它为具体的 HTTP 消息转换器提供了一种模板方法模式,用于将请求体或响应体转换为 Java 对象,或者将 Java 对象转换为响应体。

如果你需要自定义一个消息转换器,你可以扩展这个类,并实现其中的抽象方法。下面是扩展 AbstractHttpMessageConverter 的基本步骤:

  1. 确定支持的媒体类型:在构造函数中设置你的转换器将支持哪些媒体类型(例如 application/jsontext/xml 等)。

  2. 实现 supports 方法:这个方法需要判断传入的 Java 类型是否为你的转换器所支持的类型。

  3. 实现 read 方法:这个方法负责将请求体转换为 Java 对象。

  4. 实现 write 方法:这个方法负责将 Java 对象转换为响应体。

以下是一个简单的示例,展示了如何创建一个自定义的 AbstractHttpMessageConverter

import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;import java.io.IOException;
import java.nio.charset.Charset;public class CustomMessageConverter extends AbstractHttpMessageConverter<MyObject> {public CustomMessageConverter() {// 设置支持的媒体类型super(new MediaType("application", "custom", Charset.forName("UTF-8")));}@Overrideprotected boolean supports(Class<?> clazz) {// 判断传入的类型是否为 MyObject 或其子类return MyObject.class.isAssignableFrom(clazz);}@Overrideprotected MyObject readInternal(Class<? extends MyObject> clazz, HttpInputMessage inputMessage)throws IOException, HttpMessageNotReadableException {// 实现从请求体到 MyObject 的转换逻辑// ...return new MyObject();}@Overrideprotected void writeInternal(MyObject myObject, HttpOutputMessage outputMessage)throws IOException, HttpMessageNotWritableException {// 实现从 MyObject 到响应体的转换逻辑// ...}
}

在上述代码中,MyObject 是你希望转换的目标对象类型。你需要实现 readInternal 和 writeInternal 方法来完成具体的转换逻辑。

最后,不要忘记将你的自定义转换器注册到 Spring 的 HttpMessageConverter 列表中,这通常是通过配置一个 WebMvcConfigurer 来实现的:

@Configuration
public class WebConfig implements WebMvcConfigurer {@Overridepublic void configureMessageConverters(List<HttpMessageConverter<?>> converters) {converters.add(new CustomMessageConverter());}
}

这样,当 Spring MVC 处理请求和响应时,就会使用你的自定义转换器来处理 MyObject 类型的数据。

博客园里面有一篇文章写的也不错可以参考(点我)

3.2 应用

在geoserver中,设置转换器的配置代码在RestConfiguration

src/rest/src/main/java/org/geoserver/rest/RestConfiguration.java

在applicationContext.xml中可以看到扫描的是整个包下面的类

<?xml version="1.0" encoding="UTF-8"?>
<beans><!-- <mvc:annotation-driven/> --><context:component-scan base-package="org.geoserver.rest"/>
</beans>

当扫描到RestConfiguration时就会自动注册消息转换器

import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
/** Configure various aspects of Spring MVC, in particular message converters */
@Configuration
public class RestConfiguration extends WebMvcConfigurationSupport {/** 配置消息转换器 */@Overrideprotected void configureMessageConverters(List<HttpMessageConverter<?>> converters) {Catalog catalog = (Catalog) applicationContext.getBean("catalog");List<BaseMessageConverter> gsConverters =GeoServerExtensions.extensions(BaseMessageConverter.class);gsConverters.add(new FreemarkerHTMLMessageConverter("UTF-8"));gsConverters.add(new XStreamXMLMessageConverter());gsConverters.add(new XStreamJSONMessageConverter());gsConverters.add(new XStreamCatalogListConverter.XMLXStreamListConverter());gsConverters.add(new XStreamCatalogListConverter.JSONXStreamListConverter());gsConverters.add(new InputStreamConverter());EntityResolver entityResolver = catalog.getResourcePool().getEntityResolver();for (StyleHandler sh : Styles.handlers()) {for (Version ver : sh.getVersions()) {gsConverters.add(new StyleReaderConverter(sh.mimeType(ver), ver, sh, entityResolver));gsConverters.add(new StyleWriterConverter(sh.mimeType(ver), ver, sh));}}if (applicationContext.containsBean("gwcConverter")) {converters.add((HttpMessageConverter<?>) applicationContext.getBean("gwcConverter"));}gsConverters.sort(Comparator.comparingInt(BaseMessageConverter::getPriority));for (BaseMessageConverter converter : gsConverters) {converters.add(converter);}converters.removeIf(Jaxb2RootElementHttpMessageConverter.class::isInstance);converters.add(0, new Jaxb2RootElementHttpMessageConverter());super.addDefaultHttpMessageConverters(converters);}
}

上面的一对转换器都是针对geoserver一些特定对象的封装,像workspace、layer、datastore等,最下面那个比较特殊,也是比较常见的一个,它用于将java对象转换成json或者xml返回给前端

converters.add(0, new Jaxb2RootElementHttpMessageConverter());

比如果当请求工作空间时一般有下面的几种请求

http://localhost:8080/geoserver/rest/workspaces   (浏览器预览居多)
或
http://localhost:8080/geoserver/rest/workspaces.json (作为前端调用的接口居多)
或
http://localhost:8080/geoserver/rest/workspaces.xml  (作为前端调用的接口居多)

Jaxb2RootElementHttpMessageConverter 转换器,会根据前端请求的Accept请求头自动适配出前端需要的格式

其优先级是 格式拼接到请求地址上(http://localhost:8080/gisserver/rest/workspaces.json)大于 请求地址什么都不加 但是header有Accept参数

四、前置知识点-AbstractDecorator

4.1描述

 org.geotools.util.decorate.AbstractDecorator 是 GeoTools 库中的一个类,它提供了一个基础实现,用于创建装饰者模式(Decorator Pattern)的装饰器。装饰者模式允许你动态地给一个对象添加额外的职责,而不需要修改其原有的代码。通俗来说就是子类定义一个delegate变量,在子类方法中直接代用父类的方法,并且这个变量一般是通过依赖注入的,不用单独的给赋值。

在 GeoTools 中,AbstractDecorator 类是一个抽象类,它实现了 Decorator 接口,并提供了一个构造函数,接受一个要装饰的对象作为参数。这个被装饰的对象通常是一个接口的实现,而 AbstractDecorator 类则负责将所有的调用委派给这个对象。

举例:

import org.geotools.util.decorate.AbstractDecorator;public class MyDecorator extends AbstractDecorator<MyInterface> {public MyDecorator(MyInterface delegate) {super(delegate);}@Overridepublic void doSomething() {// 在调用原有方法之前,可以添加一些额外的逻辑System.out.println("Before doing something");// 调用被装饰对象的方法delegate.doSomething();// 在调用原有方法之后,也可以添加一些额外的逻辑System.out.println("After doing something");}
}public interface MyInterface {void doSomething();
}public class MyImplementation implements MyInterface {@Overridepublic void doSomething() {System.out.println("Doing something");}
}public class Main {public static void main(String[] args) {MyInterface myImplementation = new MyImplementation();MyDecorator myDecorator = new MyDecorator(myImplementation);myDecorator.doSomething();}
}

4.2 应用

AbstractDecorator 的目的主要是为了理解WorkspaceController类

从源码中可以看到

public class WorkspaceController extends AbstractCatalogController {private static final Logger LOGGER = Logging.getLogger(WorkspaceController.class);@Autowiredpublic WorkspaceController(@Qualifier("catalog") Catalog catalog) {super(catalog);}
}

扩展的说一下@Autowired是个依赖注入,

构造函数中有一个catalog,但是WorkspaceController是个servlet接口,没有实例化的地方,构造函数怎么能够传过来呢,查看applicationContext.xml可以看到

<alias name="localWorkspaceCatalog" alias="catalog"/>     
<bean id="localWorkspaceCatalog" class="org.geoserver.catalog.impl.LocalWorkspaceCatalog"><constructor-arg ref="advertisedCatalog" />
</bean><bean id="advertisedCatalog" class="org.geoserver.catalog.impl.AdvertisedCatalog"><constructor-arg ref="secureCatalog" /><property name="layerGroupVisibilityPolicy"><bean id="org.geoserver.catalog.LayerGroupVisibilityPolicy.HIDE_NEVER" class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean"/></property>
</bean>   

catalog就这通过applicationContext.xml的配置实现初始情况下就可以构造函数注入进去

再继续看LocalWorkspaceCatalog ,跟踪源码可以看到下面的代码

public class LocalWorkspaceCatalog extends AbstractCatalogDecorator implements Catalog {}public class AbstractCatalogDecorator extends AbstractDecorator<Catalog> implements Catalog {public AbstractCatalogDecorator(Catalog catalog) {super(catalog);}
}// 反编译的AbstractDecorator 
public class AbstractDecorator<D> implements Wrapper, Serializable {protected D delegate;public AbstractDecorator(D delegate) {if (delegate == null) {throw new NullPointerException("Cannot delegate to a null object");} else {this.delegate = delegate;}}
}

通过一步步的查看父对象可以看到最终继承自 org.geotools.util.decorate.AbstractDecorator ,也就是说可以直接用delegate去操作父类的一些操作

五、工作空间查询解读

一般来说工作空间的查询地址是

http://localhost:8080/geoserver/rest/workspaces   (浏览器预览居多)
或
http://localhost:8080/geoserver/rest/workspaces.json (作为前端调用的接口居多)
或
http://localhost:8080/geoserver/rest/workspaces.xml  (作为前端调用的接口居多)

当浏览器访问http://localhost:8080/geoserver/rest/workspaces的servlet代码位置在如下位置(✈ 引申的说一下,geoserver的rest代码大多在 gs-restconfig 包下面)

src/restconfig/src/main/java/org/geoserver/rest/catalog/WorkspaceController.java

    @GetMappingpublic RestWrapper workspacesGet() {List<WorkspaceInfo> wkspaces = catalog.getWorkspaces();return wrapList(wkspaces, WorkspaceInfo.class);}

@GetMapping 能看出来它是个普通的spring servlet接口,RestWrapper是对返回结果的一个包装器,catalog是针对geoserver文件目录映射出来的一个方法类

查询结果是这样的

如果不用包装器的话返回结果是这样的

    @GetMapping("/details")public List<WorkspaceInfo> getAllWorkspacesDetails() {List<WorkspaceInfo> workspaces = catalog.getWorkspaces();return workspaces;}

可以看出来如果不用包装器的话会把查出的数据原封不动的返回出来,而且兼容xml和json,实际不管使用不使用包装器时上面 三、前置知识点-AbstractHttpMessageConverter 讲到Jaxb2RootElementHttpMessageConverter 转换器都会生效,也就是说一直支持xml个json请求,而当使用包装器时就用到了另一个模板框架二、前置知识点-FreeMarker

5.1 模板解读

往下看wrapList源码

    protected <T> RestWrapper<T> wrapList(Collection<T> list, Class<T> clazz) {return new RestListWrapper<>(list, clazz, this, getTemplate(list, clazz));}

这里面终于找到了一个跟模板相关的东西getTemplate(list, clazz)

在WorkspaceController的基类RestBaseController中找到下面获取模板的代码 

    protected Template getTemplate(Object o, Class<?> clazz) {Template template = null;Configuration configuration = createConfiguration(clazz);。。。。。。(此处省略n行代码)return tryLoadTemplate(configuration, templateName);}

里面的代码看着没啥营养我替你们看过了,跟着代码就能找到模板的位置,也就是这个地方

src/restconfig/src/main/java/org/geoserver/rest/catalog/ftl-templates/workspaces.ftl

<#include "head.ftl">
Workspaces
<ul>
<#list values as w><li><a href="${page.pageURI(w.properties.name + '.html')}">${w.properties.name}</a><#if w.properties.isDefault> [default] 哈哈 </#if></li>
</#list>
</ul>
<#include "tail.ftl">

最后那两个“哈哈”是我自己加的,浏览器访问可以看到下面效果

如果你那儿是乱码的可以在头部模板里面加个<meta charset="UTF-8" />

src/restconfig/src/main/java/org/geoserver/rest/catalog/ftl-templates/head.ftl

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN""http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head><title>GeoServer Configuration</title><meta charset="UTF-8" /><meta name="ROBOTS" content="NOINDEX, NOFOLLOW"/>
</head>
<body><#setting number_format="#0.0#">

但是访问的时候我还有一个疑问,直接访问是用的模板,但当我访问json的接口时儿返回结果貌似没有走这个模板

这是因为什么呢 ,再返回去查getTemplate方法,原因是默认是根据模板名查询模板的,也就是说根据workspace就能查到模板,换成workspace.json 就不行,如果想要用json类型的模板,就得再定义个workspace.json.flt文件,

if (template == null) template = tryLoadTemplate(configuration, templateName + ".ftl");

总的来说,如果不加干预的话直接请求

http://localhost:8080/geoserver/rest/workspaces

就会使用FreeMarker模板,然后经过转换器(此处是Jaxb2RootElementHttpMessageConverter 、FreemarkerHTMLMessageConverter、XStreamXMLMessageConverter、XStreamJSONMessageConverter。。。)传给前端,如果是访问

http://localhost:8080/geoserver/rest/workspaces.json

的话则会跳过模板直接经过转换器(此处是Jaxb2RootElementHttpMessageConverter )然后传给前端

到这里FreeMarker的框架算是基本上梳理完了 ,感觉就像是个放大版的StringBuilder。

5.2 请求转换器解读

看了前面描述的三、前置知识点-AbstractHttpMessageConverter 可以知道在查询完之后会执行一次查询结果的转换操作

再次看查询工作空间的代码

    @GetMappingpublic RestWrapper workspacesGet() {List<WorkspaceInfo> wkspaces = catalog.getWorkspaces();return wrapList(wkspaces, WorkspaceInfo.class);}

🔎 下钻查看wrapList的代码如下

    protected <T> RestWrapper<T> wrapList(Collection<T> list, Class<T> clazz) {return new RestListWrapper<>(list, clazz, this, getTemplate(list, clazz));}

🔎 继续下钻查看RestListWrapper以及它的基类RestWrapperAdapter

    public void configurePersister(XStreamPersister persister, XStreamMessageConverter converter) {controller.configurePersister(persister, converter);}

从这里能看出来包装器有个关于转换器的配置的方法,而且类型是XStreamMessageConverter converter,继续跟踪代码,查找下它是在哪里被调用的

这里看到有几个继承类,但是只有里面的类型和RestListWrapper是一样的

public abstract class XStreamCatalogListConverterextends XStreamMessageConverter<RestListWrapper<?>> 

根据spring mvc的自动根据参数类型适配的原则,它用的转换器就是XStreamCatalogListConverter,而且从注释中也能看出来

/*** A wrapper for all Collection type responses using the {@link XStreamCatalogListConverter} (XML* and JSON output). Also supports Collection type responses using the {@link* FreemarkerHTMLMessageConverter}, but is not required for such responses.** <p>In the previous rest API this wasn't needed because in each individual rest request the* Collections were aliased to*/

在XStreamCatalogListConverter.java中能够看到具体的转换方法

protected void configureXStream(XStream xstream, Class<?> clazz, RestListWrapper<?> wrapper) {XStreamPersister xp = xpf.createXMLPersister();wrapper.configurePersister(xp, this);final String name = getItemName(xp, clazz);xstream.alias(name, clazz);xstream.registerConverter(new CollectionConverter(xstream.getMapper()) {@Overridepublic boolean canConvert(@SuppressWarnings("rawtypes") Class type) {return Collection.class.isAssignableFrom(type);}@Overrideprotected void writeCompleteItem(Object item,MarshallingContext context,HierarchicalStreamWriter writer) {writer.startNode(name);context.convertAnother(item);writer.endNode();}});xstream.registerConverter(new Converter() {@Overridepublic boolean canConvert(Class type) {return clazz.isAssignableFrom(type);}@Overridepublic void marshal(Object source,HierarchicalStreamWriter writer,MarshallingContext context) {String ref;// Special case for layer list, to handle the non-workspace-specific// endpoint for layersif (clazz.equals(LayerInfo.class)&& OwsUtils.getter(clazz, "prefixedName", String.class) != null&& RequestInfo.get() != null&& !RequestInfo.get().getPagePath().contains("/workspaces/")) {ref = (String) OwsUtils.get(source, "prefixedName");} else if (OwsUtils.getter(clazz, "name", String.class) != null) {ref = (String) OwsUtils.get(source, "name");} else if (OwsUtils.getter(clazz, "id", String.class) != null) {ref = (String) OwsUtils.get(source, "id");} else if (OwsUtils.getter(clazz, "id", Long.class) != null) {// For some reason Importer objects have Long ids so this catches that// caseref = OwsUtils.get(source, "id").toString();} else {throw new RuntimeException("Could not determine identifier for: " + clazz.getName());}writer.startNode(wrapper.getItemAttributeName());writer.setValue(ref);writer.endNode();encodeLink(encode(ref), writer);}@Overridepublic Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {return null;}});}

我修改了上述代码中的

writer.startNode(wrapper.getItemAttributeName() +"test");

然后再次请求接口就能看到修改后的数据

写在最后,文章难免有写的不对或者不完善的地方,欢迎提出纠正意见

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/363293.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ASUS/华硕幻14 2023 GA402X系列 原厂Windows11-22H2系统

安装后恢复到您开箱的体验界面&#xff0c;带原机所有驱动和软件&#xff0c;包括myasus mcafee office 奥创等。 最适合您电脑的系统&#xff0c;经厂家手调试最佳状态&#xff0c;性能与功耗直接拉满&#xff0c;体验最原汁原味的系统。 原厂系统下载网址&#xff1a;http:…

激光与相机融合标定汇总:提升融合算法的精度与可靠性(附github地址)

前言 随着科技的飞速发展&#xff0c;激光技术与相机技术的融合已成为推动智能化影像发展的重要力量。这种融合不仅提高了成像的精度和效率&#xff0c;还为相关行业带来了革命性的变革。在这篇博客中&#xff0c;我们将深入探讨激光与相机融合标定的原理及其在各个领域的应用…

@ComponentScan注解在Spring的作用

ComponentScan注解的作用是什么&#xff1f; 告知Spring扫描那些包下的类&#xff0c;可以配置includeFilters&#xff0c;excludeFilters&#xff0c;过滤某些类&#xff0c;更多内容可以具体看下此注解文件。 Spring是如何通过这个注解将类注入到Ioc容器中的&#xff1f; 请…

【数字基础设施1007】探索数字基础设施的影响:宽带政策变量数据集来了!

今天给大家分享的是国内顶级期刊2023年发表论文《数字基础设施与代际收入向上流动性——基于“宽带中国”战略的准自然实验》使用到的重要数据集——“宽带中国”政策变量数据、互联网发展指数以及工具变量&#xff08;所在城市到杭州市的球面距离和到“八纵八横”政策节点城市…

加密与安全_Java 加密体系 (JCA) 和 常用的开源密码库

文章目录 Java Cryptography Architecture (JCA)开源国密库国密算法对称加密&#xff08;DES/AES⇒SM4&#xff09;非对称加密&#xff08;RSA/ECC⇒SM2&#xff09;散列(摘要/哈希)算法&#xff08;MD5/SHA⇒SM3&#xff09; 在线生成公钥私钥对&#xff0c;RSA公私钥生成参考…

Docker Compose 入门

想象一下在服务器上运行静态页面的场景。对于这项任务&#xff0c;NGINX 服务器是一个不错的选择。我们在 static-site/index.html 路径下有一个简单的 HTML 文件&#xff1a; 通过使用 Docker&#xff0c;我们将使用以下官方镜像运行 NGINX 服务器 docker run --rm -p 8080:…

spring boot jar 启动报错 Zip64 archives are not supported

spring boot jar 启动报错 Zip64 archives are not supported 原因、解决方案问题为什么 spring boot 不支持 zip64zip、zip64 功能上的区别zip 的文件格式spring-boot-loader 是如何判断是否是 zip64 的&#xff1f; 参考 spring boot 版本是 2.1.8.RELEASE&#xff0c;引入以…

Hi3861 OpenHarmony嵌入式应用入门--中断按键

本篇讲解gpio的中断使用方式。 硬件原理图如下&#xff0c;与上一篇一样的电路 GPIO API API名称 说明 hi_u32 hi_gpio_init(hi_void); GPIO模块初始化 hi_u32 hi_io_set_pull(hi_io_name id, hi_io_pull val); 设置某个IO上下拉功能。 hi_u32 hi_gpio_set_dir(hi_gpio_…

汽车零部件制造企业如何选择合适的ESOP电子作业指导书系统

随着汽车产业的不断发展&#xff0c;汽车零部件制造企业在提高生产效率和产品质量方面面临着越来越大的挑战。为了解决这些问题&#xff0c;越来越多的汽车零部件制造企业开始采用ESOP电子作业指导书系统&#xff0c;以帮助他们管理和优化生产流程。但是&#xff0c;在选择合适…

three.js - MeshPhongMaterial材质(实现玻璃水晶球效果)

1、概念 phong网格材质&#xff1a;Mesh - Phong - Material 一种用于具有镜面高光的光泽表面的材质。 它可以模拟&#xff0c;具有镜面高光的光泽表面&#xff0c;提供镜面反射效果。 MeshPhongMaterial&#xff1a; MeshPhongMaterial是一种基于Phong光照模型的材质&#…

FreeRTOS信号量和互斥量

信息量 简介 信号量是一种解决同步问题的机制&#xff0c;可以实现对共享资源的有序访问。 前面介绍的队列(queue)可以用于传输数据&#xff1a;在任务之间、任务和中断之间。 消息队列用于传输多个数据&#xff0c;但是有时候我们只需要传递状态&#xff0c;这个状态值需要用…

招聘,短信与您:招聘人员完整指南

招聘人员面临的最大挑战之一就是沟通和联系候选人。为何?我们可以从以下原因开始&#xff1a;候选人通常被太多的招聘人员包围&#xff0c;试图联系他们&#xff0c;这使得你很难吸引他们的注意。在招聘过程的不同阶段&#xff0c;根据不同的工作量&#xff0c;让申请人保持最…

8.12 矢量图层面要素单一符号使用七(随机标记填充)

文章目录 前言随机标记填充&#xff08;Random Marker Fill&#xff09;QGis设置面符号为随机标记填充&#xff08;Random Marker Fill&#xff09;二次开发代码实现随机标记填充&#xff08;Random Marker Fill&#xff09; 总结 前言 本章介绍矢量图层线要素单一符号中使用随…

Swift 6:导入语句上的访问级别

文章目录 前言示例启用 AccessLevelOnImport破坏性变更采用这些更改总结前言 SE-0409 提案引入了一项新功能,即允许使用 Swift 的任何可用访问级别标记导入声明,以限制导入的符号可以在哪些类型或接口中使用。由于这些变化,现在可以将依赖项标记为对当前源文件(private 或…

element-plus 日期选择添加确定按钮

需求&#xff1a;选择日期后&#xff0c;点击确定按钮关闭面板 思路&#xff1a; 使用shortcuts自定义确定和取消按钮选择日期后使用handleOpen()强制开启面板点击确定后使用handleClose()关闭面板 <template><el-date-pickerref"pickerRef"v-model"…

龙国南方航空滑块acw_v2+cookie+风控处理+type后缀

声明 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff01; 本文章未经许可禁…

华火新能源电燃灶:电生明火,开启电能新时代

在当今能源消耗与环境保护日益受到关注的时代&#xff0c;华火新能源电燃灶“电生明火”应运而生&#xff0c;以其卓越的性能和创新的技术&#xff0c;为我们的生活带来了全新的变革。 一、安全至上 安全&#xff0c;是我们生活中最基本的需求。华火新能源电燃灶采用了先进的多…

FFmpeg+javacpp+javacv使用

FFmpegjavacppjavacv使用 Bytedeco官网案例1、导入opencv、ffmpeg依赖包2、FFmpeg 数据结构2.1 AVFormatContext 格式化I/O上下文2.1.1 metadata2.1.2 Duration、start、bitrate等其他信息2.1.3 dump信息 Bytedeco GitHub&#xff1a;javacpp Bytedeco官网案例 FFmpeg – [示例…

基于 SpringBoot + Vue 的图书购物商城项目

本项目是一个基于 SpringBoot 和 Vue 的图书购物商城系统。系统主要实现了用户注册、登录&#xff0c;图书浏览、查询、加购&#xff0c;购物车管理&#xff0c;订单结算&#xff0c;会员折扣&#xff0c;下单&#xff0c;个人订单管理&#xff0c;书籍及分类管理&#xff0c;用…

一个AI图片生成工具导航网站

上周末上线了一个AI图片生成工具导航网站&#xff0c;主要是面向AI图片工具这个垂直领域。 https://chatgpt-image-generator.com/ 目标是通过收集当下的一些工具&#xff0c;然后进行分类管理&#xff0c;一方面方便大家发现新的工具&#xff0c;另一方面能够更加有针对性、…