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 的基本步骤:
-
确定支持的媒体类型:在构造函数中设置你的转换器将支持哪些媒体类型(例如
application/json,text/xml等)。 -
实现
supports方法:这个方法需要判断传入的 Java 类型是否为你的转换器所支持的类型。 -
实现
read方法:这个方法负责将请求体转换为 Java 对象。 -
实现
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");
然后再次请求接口就能看到修改后的数据

写在最后,文章难免有写的不对或者不完善的地方,欢迎提出纠正意见
相关文章:
Geoserver源码解读四 REST服务
文章目录 文章目录 一、概要 二、前置知识点-FreeMarker 三、前置知识点-AbstractHttpMessageConverter 3.1 描述 3.2 应用 四、前置知识点-AbstractDecorator 4.1描述 4.2 应用 五、工作空间查询解读 5.1 模板解读 5.2 请求转换器解读 一、概要 关于geoserver的r…...
硬件开发笔记(二十一):外部搜索不到的元器件封装可尝试使用AD21软件的“ManufacturerPart Search”功能
若该文为原创文章,转载请注明原文出处 本文章博客地址:https://hpzwl.blog.csdn.net/article/details/139869584 长沙红胖子Qt(长沙创微智科)博文大全:开发技术集合(包含Qt实用技术、树莓派、三维、OpenCV…...
【AI大模型】GPTS 与 Assistants API
前言 2023 年 11 月 6 日,OpenAI DevDay 发表了一系列新能力,其中包括:GPT Store 和 Assistants API。 GPTs 和 Assistants API 本质是降低开发门槛 可操控性和易用性之间的权衡与折中: 更多技术路线选择:原生 API、…...
攻击者开始使用 XLL 文件进行攻击
近期,研究人员发现使用恶意 Microsoft Excel 加载项(XLL)文件发起攻击的行动有所增加,这项技术的 MITRE ATT&CK 技术项编号为 T1137.006。 这些加载项都是为了使用户能够利用高性能函数,为 Excel 工作表提供 API …...
Why RAG is slower than LLM?
I used RAG with LLAMA3 for AI bot. I find RAG with chromadb is much slower than call LLM itself. Following the test result, with just one simple web page about 1000 words, it takes more than 2 seconds for retrieving: 我使用RAG(可能是指某种特定的…...
Word页码设置,封面无页码,目录摘要阿拉伯数字I,II,III页码,正文开始123为页码
一、背景 使用Word写项目书或论文时,需要正确插入页码,比如封面无页码,目录摘要阿拉伯数字I,II,III为页码,正文开始以123为页码,下面介绍具体实施方法。 所用Word版本:2021 二、W…...
汽车汽配图纸管理、产品研发管理解决方案
汽车汽配图纸管理、产品研发管理解决方案 随着全球汽车市场的快速发展,中国汽车汽配行业迎来了前所未有的发展机遇。然而,在这一过程中,企业也面临着诸多挑战,如研发能力的提升、技术资料管理的复杂性、以及跨部门协作的困难等。为…...
小程序简单版音乐播放器
小程序简单版音乐播放器 结构 先来看看页面结构 <!-- wxml --><!-- 标签页标题 --> <view class"tab"><view class"tab-item {{tab0?active:}}" bindtap"changeItem" data-item"0">音乐推荐</view><…...
驾校预约管理系统
摘 要 随着驾驶技术的普及和交通安全意识的增强,越来越多的人选择参加驾校培训,以获取驾驶执照。然而,驾校管理面临着日益增长的学员数量和繁琐的预约管理工作。为了提高驾校的管理效率和服务质量,驾校预约管理系统成为了必不可少…...
C++ 左值右值 || std::move() || 浅拷贝,深拷贝 || 数据类型
数据类型: 作用:决定变量所占内存空间的字节大小,和布局方式基本数据类型: 算数类型: 整形(bool / char……扩展集 / int / long……)&& 浮点形(float/double……ÿ…...
发那科机器人IO 分配
IO 信号 也称为输入\输出信号,是机器人与外围设备通信的电信号...
ubuntu开机怎么进入、退出命令行界面
要在Ubuntu系统开机时进入命令行界面,可以按照以下步骤操作: 在开机过程中按下Ctrl Alt F1组合键,这将会切换到第一个虚拟控制台,即命令行界面。如果Ctrl Alt F1没有生效,也可以尝试Ctrl Alt F2、Ctrl Alt F3…...
『FPGA通信接口』LVDS接口(4)LVDS接收端设计
文章目录 1.LVDS接收端概述2逻辑框图3.xapp855训练代码解读4.接收端发送端联调5.传送门 1.LVDS接收端概述 接收端的传输模型各个属性应该与LVDS发送端各属性一致,例如,如果用于接收CMOS图像传感器的图像数据,则接收端程序的串化因子、通道个…...
面试题:HTTP的body是二进制还是文本
实际上,HTTP的body可以是二进制数据,也可以是文本。HTTP协议本身不对body内容的格式做限制,具体格式取决于Content-Type头字段的定义。 文本数据: 当Content-Type头字段指定为文本类型时(如text/plain、text/html、ap…...
5分钟带你部署一套Jenkins持续集成环境
5分钟带你部署一套Jenkins持续集成环境 Jenkins是开源CI&CD软件领导者, 提供超过1000个插件来支持构建、部署、自动化, 满足任何项目的需要。 Jenkins的优点 持续集成和持续交付 作为一个可扩展的自动化服务器,Jenkins 可以用作简单的 CI…...
OpenAI突然宣布停止向中国提供API服务!
标题 🌟 OpenAI突然宣布停止向中国提供API服务! 🌟摘要 📜引言 📢正文 📝1. OpenAI API的重要性2. 停止服务的原因分析3. 对中国市场的影响4. 应对措施代码案例 📂常见问题解答(QA)❓…...
Bootstrap 标签
Bootstrap 标签 引言 Bootstrap 是一个流行的前端框架,它提供了一套丰富的组件和工具,帮助开发者快速构建响应式和移动优先的网页。在 Bootstrap 中,标签(Badge)是一种小巧的组件,用于显示计数、提示或标…...
EtherCAT主站SOEM -- 37 -- win-soem-win10及win11系统QT-SOEM-1个电机转圈圈-周期同步速度模式(CSV模式)
EtherCAT主站SOEM -- 37 -- win-soem-win10及win11系统QT-SOEM-1个电机转圈圈-周期同步速度模式(CSV模式) 0 QT-SOEM及STM32F767-SOEM视频欣赏及源代码链接:0.1 Linux--Ubuntu系统之 QT-SOEM博客、视频欣赏及源代码链接0.2 STM32F767-SOEM 博客、视频欣赏及源代码链接0.3 wi…...
老板舍不得买库存管理软件❓一招解决
在当今快节奏的商业环境中,仓库管理是企业运作中不可或缺的一环。对于许多中小型企业而言,简易且高效的库存管理系统尤为重要。搭贝简易库存管理系统针对仓库的出入库进行有效管理,帮助企业实现库存的透明化和流程的自动化。 客户的痛点 1. …...
【MySQL数据库】:MySQL视图特性
目录 视图的概念 基本使用 准备测试表 创建视图 修改视图影响基表 修改基表影响视图 删除视图 视图规则和限制 视图的概念 视图是一个虚拟表,其内容由查询定义,同真实的表一样,视图包含一系列带有名称的列和行数据。视图中的数据…...
Linux相关概念和易错知识点(42)(TCP的连接管理、可靠性、面临复杂网络的处理)
目录 1.TCP的连接管理机制(1)三次握手①握手过程②对握手过程的理解 (2)四次挥手(3)握手和挥手的触发(4)状态切换①挥手过程中状态的切换②握手过程中状态的切换 2.TCP的可靠性&…...
【JVM】- 内存结构
引言 JVM:Java Virtual Machine 定义:Java虚拟机,Java二进制字节码的运行环境好处: 一次编写,到处运行自动内存管理,垃圾回收的功能数组下标越界检查(会抛异常,不会覆盖到其他代码…...
CocosCreator 之 JavaScript/TypeScript和Java的相互交互
引擎版本: 3.8.1 语言: JavaScript/TypeScript、C、Java 环境:Window 参考:Java原生反射机制 您好,我是鹤九日! 回顾 在上篇文章中:CocosCreator Android项目接入UnityAds 广告SDK。 我们简单讲…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...
在Ubuntu24上采用Wine打开SourceInsight
1. 安装wine sudo apt install wine 2. 安装32位库支持,SourceInsight是32位程序 sudo dpkg --add-architecture i386 sudo apt update sudo apt install wine32:i386 3. 验证安装 wine --version 4. 安装必要的字体和库(解决显示问题) sudo apt install fonts-wqy…...
GruntJS-前端自动化任务运行器从入门到实战
Grunt 完全指南:从入门到实战 一、Grunt 是什么? Grunt是一个基于 Node.js 的前端自动化任务运行器,主要用于自动化执行项目开发中重复性高的任务,例如文件压缩、代码编译、语法检查、单元测试、文件合并等。通过配置简洁的任务…...
【笔记】WSL 中 Rust 安装与测试完整记录
#工作记录 WSL 中 Rust 安装与测试完整记录 1. 运行环境 系统:Ubuntu 24.04 LTS (WSL2)架构:x86_64 (GNU/Linux)Rust 版本:rustc 1.87.0 (2025-05-09)Cargo 版本:cargo 1.87.0 (2025-05-06) 2. 安装 Rust 2.1 使用 Rust 官方安…...
免费PDF转图片工具
免费PDF转图片工具 一款简单易用的PDF转图片工具,可以将PDF文件快速转换为高质量PNG图片。无需安装复杂的软件,也不需要在线上传文件,保护您的隐私。 工具截图 主要特点 🚀 快速转换:本地转换,无需等待上…...
MySQL的pymysql操作
本章是MySQL的最后一章,MySQL到此完结,下一站Hadoop!!! 这章很简单,完整代码在最后,详细讲解之前python课程里面也有,感兴趣的可以往前找一下 一、查询操作 我们需要打开pycharm …...
Linux 下 DMA 内存映射浅析
序 系统 I/O 设备驱动程序通常调用其特定子系统的接口为 DMA 分配内存,但最终会调到 DMA 子系统的dma_alloc_coherent()/dma_alloc_attrs() 等接口。 关于 dma_alloc_coherent 接口详细的代码讲解、调用流程,可以参考这篇文章,我觉得写的非常…...
