Vert.x整合MongoDB实现通用增删改查

文章目录

  • 前言
  • 一、框架搭建
  • 二、代码编写
    • 1.MongoDB连接
    • 2.通用CRUD
    • 3.HTTP服务
  • 总结


前言

最近在学习MongoDB的基本功能,只看文档过于无趣了,于是用Vert.x写了个demo加深印象,也在此记录一下。


一、框架搭建

Vert.x使用的版本是4.4.4,主要用到了vertx-web和vertx-mongo-client。

<properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.compiler.source>1.8</maven.compiler.source><maven.compiler.target>1.8</maven.compiler.target><maven-compiler-plugin.version>3.8.1</maven-compiler-plugin.version><maven-shade-plugin.version>3.2.4</maven-shade-plugin.version><maven-surefire-plugin.version>2.22.2</maven-surefire-plugin.version><exec-maven-plugin.version>3.0.0</exec-maven-plugin.version><vertx.version>4.4.4</vertx.version><junit-jupiter.version>5.9.1</junit-jupiter.version><logback.version>1.2.3</logback.version><jackson.version>2.11.3</jackson.version><main.verticle>com.whty.mongodemo.MainVerticle</main.verticle><launcher.class>io.vertx.core.Launcher</launcher.class></properties><dependencyManagement><dependencies><dependency><groupId>io.vertx</groupId><artifactId>vertx-stack-depchain</artifactId><version>${vertx.version}</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement><dependencies><dependency><groupId>io.vertx</groupId><artifactId>vertx-config</artifactId></dependency><dependency><groupId>io.vertx</groupId><artifactId>vertx-web</artifactId></dependency><dependency><groupId>io.vertx</groupId><artifactId>vertx-mongo-client</artifactId></dependency><dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>${logback.version}</version></dependency><dependency><groupId>io.vertx</groupId><artifactId>vertx-junit5</artifactId><scope>test</scope></dependency><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter-api</artifactId><version>${junit-jupiter.version}</version><scope>test</scope></dependency><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter-engine</artifactId><version>${junit-jupiter.version}</version><scope>test</scope></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.24</version></dependency><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>${jackson.version}</version></dependency></dependencies>

二、代码编写

熟悉Vert.x的同学应该都知道,其基本单元是Verticle,本demo中包含三个Verticle,分别是MainVerticle、MongoVerticle、HttpServerVerticle,MainVerticle是程序的入口,在其中依次发布另外两个Verticle。

1.MongoDB连接

在MongoVerticle中,我们主要是建立MongoDB连接和添加EventBus监听。

import io.vertx.core.AbstractVerticle;public class MongoVerticle extends AbstractVerticle {@Overridepublic void start() {new MongoListener(vertx.eventBus(), new MongoClientFactory(vertx).getInstance());}
}

MongoDB的连接建立非常简单,我们通过一个工厂类去创建一个连接。

public class MongoClientFactory {private final MongoClient client;public MongoClientFactory(Vertx vertx) {JsonObject config = new JsonObject().put("db_name", "demo").put("host", "127.0.0.1").put("port", 27017).put("username", "demo").put("password", "demo123").put("authSource", "demo").put("useObjectId", true);this.client = MongoClient.createShared(vertx, config);}public MongoClient getInstance() {return this.client;}
}

需要注意的是数据库的配置,useObjectId表示插入数据时如果无_id则自动生成ObjectId,ObjectId在JSON显示如下

{"_id":{"$oid":"xxxxxxxxxxxxxxxxxxxxx"}}

如果将useObjectId设置为true则在后续API中会自动转换为

{"_id":"xxxxxxxxxxxxxxxxxx"}

useObjectId默认设置为false将会使用普通字符串id。

2.通用CRUD

vertx-mongo-client中的API非常丰富,详细可以参考官方文档,本demo中仅列举基本CRUD操作。

import com.whty.mongodemo.constant.EventBusAddress;
import com.whty.mongodemo.constant.ParamKey;
import com.whty.mongodemo.util.DateUtil;
import io.vertx.core.Future;
import io.vertx.core.eventbus.EventBus;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.mongo.FindOptions;
import io.vertx.ext.mongo.MongoClient;
import io.vertx.ext.mongo.MongoClientDeleteResult;
import io.vertx.ext.mongo.UpdateOptions;
import io.vertx.ext.mongo.impl.codec.json.JsonObjectCodec;
import lombok.extern.slf4j.Slf4j;
import java.util.List;@Slf4j
public class MongoListener {private final EventBus eventBus;private final MongoClient client;public MongoListener(EventBus eventBus, MongoClient client) {this.eventBus = eventBus;this.client = client;this.addConsumer();}private void addConsumer() {this.queryById();this.query();this.insert();this.remove();this.update();this.queryByPage();this.queryWithLookup();}private void queryById() {eventBus.localConsumer(EventBusAddress.QUERY_BY_ID, message -> {JsonObject body = (JsonObject) message.body();String collection = body.getString(ParamKey.COLLECTION);JsonObject query = body.getJsonObject(ParamKey.QUERY);client.find(collection, query, ar -> {if (ar.succeeded()) {List<JsonObject> list = ar.result();if (list != null && list.size() > 0) {JsonObject result = list.get(0);message.reply(this.handleDate(result));} else {message.reply(null);}} else {log.error(ar.cause().getMessage(), ar.cause());message.reply(ar.cause());}});});}private void query() {eventBus.localConsumer(EventBusAddress.QUERY, message -> {JsonObject body = (JsonObject) message.body();String collection = body.getString(ParamKey.COLLECTION);JsonObject query = body.getJsonObject(ParamKey.QUERY);client.find(collection, query, ar -> {if (ar.succeeded()) {List<JsonObject> list = ar.result();if (list != null && list.size() > 0) {list.forEach(this::handleDate);message.reply(new JsonArray(list));} else {message.reply(new JsonArray());}} else {log.error(ar.cause().getMessage(), ar.cause());message.reply(ar.cause());}});});}private void queryByPage() {eventBus.localConsumer(EventBusAddress.QUERY_BY_PAGE, message -> {JsonObject body = (JsonObject) message.body();String collection = body.getString(ParamKey.COLLECTION);JsonObject query = body.getJsonObject(ParamKey.QUERY);JsonObject pageParams = body.getJsonObject(ParamKey.PAGE);Integer pageSIze = pageParams.getInteger(ParamKey.PAGE_SIZE);Integer pageNum = pageParams.getInteger(ParamKey.PAGE_NUM);JsonObject sort = pageParams.getJsonObject(ParamKey.SORT);FindOptions findOptions = new FindOptions().setSort(sort).setLimit(pageSIze).setSkip((pageNum - 1) * pageSIze);Future<Long> countFuture = client.count(collection, query);Future<List<JsonObject>> dataFuture = client.findWithOptions(collection, query, findOptions);Future.all(countFuture, dataFuture).onSuccess(ar -> {Long count = ar.resultAt(0);List<JsonObject> list = ar.resultAt(1);if (list != null && list.size() > 0) {list.forEach(this::handleDate);}JsonObject result = new JsonObject().put(ParamKey.PAGE_SIZE, pageSIze).put(ParamKey.PAGE_NUM, pageNum).put("total", count).put("list", list != null && list.size() > 0 ? list : new JsonArray());message.reply(result);}).onFailure(e -> {log.error(e.getMessage(), e);message.reply(e);});});}private void insert() {eventBus.localConsumer(EventBusAddress.INSERT, message -> {JsonObject body = (JsonObject) message.body();String collection = body.getString(ParamKey.COLLECTION);JsonObject query = body.getJsonObject(ParamKey.QUERY);query.put("createTime", new JsonObject().put("$date", DateUtil.getUTCTime()));try {client.insert(collection, query, ar -> {if (ar.succeeded()) {String id = ar.result();message.reply(new JsonObject().put("_id", id));} else {log.error("存储数据异常:{}", ar.cause().getMessage(), ar.cause());message.reply(ar.cause());}});} catch (Exception e) {log.error("存储数据异常:{}", e.getMessage(), e);message.reply(e);}});}private void update() {eventBus.localConsumer(EventBusAddress.UPDATE, message -> {JsonObject body = (JsonObject) message.body();String collection = body.getString(ParamKey.COLLECTION);JsonObject query = body.getJsonObject(ParamKey.QUERY);JsonObject data = body.getJsonObject("data");data.put("updateTime", new JsonObject().put("$date", DateUtil.getUTCTime()));JsonObject update = new JsonObject().put("$set", data);//multi set to true to update multiple documentsclient.updateCollectionWithOptions(collection, query, update,new UpdateOptions().setMulti(body.containsKey("multi")),ar -> {if (ar.succeeded()) {message.reply(ar.result().toJson());} else {log.error(ar.cause().getMessage(), ar.cause());message.reply(ar.cause());}});});}private void remove() {eventBus.localConsumer(EventBusAddress.REMOVE, message -> {JsonObject body = (JsonObject) message.body();String collection = body.getString(ParamKey.COLLECTION);JsonObject query = body.getJsonObject(ParamKey.QUERY);Future<MongoClientDeleteResult> future;if (body.containsKey(ParamKey.SINGLE)) { //仅删除匹配的第一条数据future = client.removeDocument(collection, query);} else {future = client.removeDocuments(collection, query);}future.andThen(ar -> {if (ar.succeeded()) {message.reply(ar.result().toJson());} else {log.error(ar.cause().getMessage(), ar.cause());message.reply(ar.cause());}});});}private void queryWithLookup() {eventBus.localConsumer(EventBusAddress.QUERY_WITH_LOOKUP, message -> {JsonObject body = (JsonObject) message.body();String collection = body.getString(ParamKey.COLLECTION);JsonObject query = body.getJsonObject(ParamKey.QUERY);JsonArray pipeline = new JsonArray();JsonObject lookup = new JsonObject().put("$lookup", body.getJsonObject("lookup"));pipeline.add(lookup);if (null != query) {pipeline.add(new JsonObject().put("$match", query));}JsonArray resultArr = new JsonArray();client.aggregate(collection, pipeline).handler(resultArr::add).endHandler(v ->message.reply(resultArr)).exceptionHandler(e -> {log.error(e.getMessage(), e);message.reply(e);});});}private JsonObject handleDate(JsonObject json) {return this.decodeDateKey(this.decodeDateKey(json, "createTime"), "updateTime");}private JsonObject decodeDateKey(JsonObject json, String key) {Object timeField = json.getValue(key, null);if (!(timeField instanceof JsonObject)) return json;Object timeString = ((JsonObject) timeField).getValue(JsonObjectCodec.DATE_FIELD, null);if (!(timeString instanceof String)) return json;json.put(key, DateUtil.formatOffsetDateTime((String) timeString));return json;}
}

Vert.x的Verticle之间主要通过EventBus进行通信,在这里我们创建了MongoDB的通用CRUD监听,而发送消息的一段则在后面的HttpServerVerticle中创建。

可以看到每一个API都会用到collection参数,collection集合就相当于SQL数据库的表,这里collection参数表示表名称,通过传递不同的表名称来对不同的表进行CRUD。

$lookup是MongoDB中的高阶查询方法,可以实现类似SQL数据库中的联表功能,具体用法可以参考这篇文章。

3.HTTP服务

import com.whty.mongodemo.route.CustomForm;
import com.whty.mongodemo.route.FormData;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.http.HttpServer;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.handler.BodyHandler;
import lombok.extern.slf4j.Slf4j;@Slf4j
public class HttpServerVerticle extends AbstractVerticle {private final int port = 8080;@Overridepublic void start() {HttpServer server = vertx.createHttpServer();Router router = Router.router(vertx);router.route().handler(BodyHandler.create());//注册路由this.registerRoute(router);server.requestHandler(router).listen(port, ar -> {if (ar.succeeded()) {log.info("web服务启动成功,监听端口{}", port);} else {log.error("web服务启动失败");}});}private void registerRoute(Router router) {new CustomForm().init(router);new FormData().init(router);}
}

在这个Verticle中,我们创建了HttpServer并注册了路由,我们演示的集合有两个分别是custom_form和form_data,一个用于存储自定义表单结构,另一个存储表单填写的数据,这两个集合的业务路由分别由两个类管理。

import com.whty.mongodemo.handler.CustomFormHandler;
import io.vertx.ext.web.Router;
import lombok.extern.slf4j.Slf4j;@Slf4j
public class CustomForm {private final CustomFormHandler handler = new CustomFormHandler();private final static String prefix = "/customForm";public void init(Router router) {router.get(prefix + "/queryById/:id").handler(handler.queryById());router.post(prefix + "/query").handler(handler.query());router.post(prefix + "/queryByPage").handler(handler.queryByPage());router.post(prefix + "/insert").handler(handler.insert());router.put(prefix + "/update").handler(handler.update());router.delete(prefix + "/remove/:id").handler(handler.remove());router.post(prefix + "/queryWithChildren").handler(handler.queryWithChildren());}
}

为了传参方便,查询列表和分页查询也使用的是post方法,不太符合REST规范建议好孩子不要学习。下面继续看handler中的代码

import com.whty.mongodemo.constant.ParamKey;
import com.whty.mongodemo.service.CustomFormService;
import io.vertx.core.Handler;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.RoutingContext;
import lombok.extern.slf4j.Slf4j;@Slf4j
public class CustomFormHandler extends BaseRoutingHandler {private final CustomFormService service = new CustomFormService("form_schema");public Handler<RoutingContext> queryById() {return ctx -> service.queryById(ctx.pathParam("id"), this.commonReplyHandler(ctx.response()));}public Handler<RoutingContext> query() {return ctx -> service.query(ctx.body() != null ? ctx.body().asJsonObject() : null, this.commonReplyHandler(ctx.response()));}public Handler<RoutingContext> queryByPage() {return ctx -> {JsonObject queryParams = ctx.body().asJsonObject();JsonObject page = new JsonObject().put(ParamKey.PAGE_NUM, 1).put(ParamKey.PAGE_SIZE, 10);JsonObject query = new JsonObject();if (null != queryParams) {queryParams.forEach(entry -> {String k = entry.getKey();Object v = entry.getValue();if (k.equals(ParamKey.PAGE_NUM)) {page.put(ParamKey.PAGE_NUM, v);} else if (k.equals(ParamKey.PAGE_SIZE)) {page.put(ParamKey.PAGE_SIZE, v);} else {if ("formRef".equals(k)) { //模糊查询query.put(k, new JsonObject().put("$regex", v));} else {query.put(k, v);}}});}service.queryByPage(page, query, this.commonReplyHandler(ctx.response()));};}public Handler<RoutingContext> queryWithChildren() {return ctx -> {JsonObject lookup = new JsonObject().put("from", "form_component").put("localField", "component_id").put("foreignField", "_id").put("as", "component_info");service.queryWithLookup(lookup, ctx.body().asJsonObject(), this.commonReplyHandler(ctx.response()));};}public Handler<RoutingContext> insert() {return ctx -> {JsonObject query = ctx.body().asJsonObject();service.insert(query, this.commonReplyHandler(ctx.response()));};}public Handler<RoutingContext> update() {return ctx -> {JsonObject bodyData = ctx.body().asJsonObject();String id = bodyData.getString("_id");bodyData.remove("_id");service.updateById(id, bodyData, this.commonReplyHandler(ctx.response()));};}public Handler<RoutingContext> remove() {return ctx -> service.removeById(ctx.pathParam("id"), this.commonReplyHandler(ctx.response()));}
}

每个业务handler都会继承BaseRoutingHandler,其中包含一些通用方法

import com.whty.mongodemo.model.JsonResult;
import io.vertx.core.AsyncResult;
import io.vertx.core.Handler;
import io.vertx.core.eventbus.Message;
import io.vertx.core.http.HttpHeaders;
import io.vertx.core.http.HttpServerResponse;
import lombok.extern.slf4j.Slf4j;@Slf4j
public class BaseRoutingHandler {public void endJson(HttpServerResponse response, JsonResult jsonResult) {response.putHeader(HttpHeaders.CONTENT_TYPE, "application/json; charset=utf-8");response.end(jsonResult.encodePrettily());}public void endJson(HttpServerResponse response) {response.putHeader(HttpHeaders.CONTENT_TYPE, "application/json; charset=utf-8");response.end();}public Handler<AsyncResult<Message<Object>>> commonReplyHandler(HttpServerResponse response) {return ar -> {if (ar.succeeded()) {if (null != ar.result().body() && ar.result().body() instanceof Throwable) {this.endJson(response, JsonResult.failed());} else {this.endJson(response, JsonResult.success(ar.result().body()));}} else {log.error(ar.cause().getMessage(), ar.cause());this.endJson(response, JsonResult.failed());}};}
}

form_data表相关的代码与以上代码非常相似就不贴了。可以看到整个项目都是JSON直通到底没有使用实体类,这也是为了演示MongoDB库数据结构灵活的特点,在实际生产中,还是建议规范定义schema,创建实体类传参,避免数据混乱。


总结

以上就是Vert.x操作MongoDB的示例代码,对Vert.x感兴趣的同学可以看看本人其他相关文章,下面是本项目源码,可以顺手点个STAR。
源码下载

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

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

相关文章

《论文阅读》具有特殊Token和轮级注意力的层级对话理解 ICLR 2023

《论文阅读》具有特殊Token和轮级注意力的层级对话理解 前言简介问题定义模型构建知识点Intra-turn ModelingInter-turn Modeling分类前言 你是否也对于理解论文存在困惑? 你是否也像我之前搜索论文解读,得到只是中文翻译的解读后感到失望? 小白如何从零读懂论文?和我一…

MySQL事务

目录 前言 1.为什么存在事务 2.什么是事务 3.事务的版本支持 4.事务提交方式 5.事务常见操作方式 6.事务隔离级别 6.1如何理解隔离性 6.2隔离级别 6.3隔离性的查看与设置 6.4读未提交 6.5读提交 6.6可重复读 6.7串行化 7.多版本并发控制 7.1 3个记录隐藏列字段…

面向对象编程:从创建类到封装与构造方法的探索

1. 代码如何创建类&#xff1f; 在面向对象编程中&#xff0c;类是对一类事物的抽象&#xff0c;包含了静态的属性&#xff08;成员变量&#xff09;和动态的行为&#xff08;成员方法&#xff09;。在Java中&#xff0c;创建类的格式如下&#xff1a; 修饰词 class 类名 {//…

办公楼管理高手:一起来学烟雾监测实用技能!

在现代社会中&#xff0c;安全意识和防患意识越来越受到重视。特别是在大型办公楼等人员密集的场所&#xff0c;火灾的风险不容忽视。 为了保障员工和资产的安全&#xff0c;烟感监控成为一项至关重要的安全措施。烟感监控系统作为火灾预警的关键组成部分&#xff0c;能够及早发…

ardupilot 悬停油门学习

目录 文章目录 目录摘要1.悬停油门更新2.如何开启悬停油门学习3.日志记录悬停油门的变化摘要 本节主要记录ardupilot的悬停油门相关知识,正常悬停油门更新:没有解锁和着地不更新,手动模式或者漂移模式不更新,垂直方向有期望速度不更新,只有油门大于最低值,垂直运动速度小…

大数据Flink(四十九):框架版本介绍和编程语言选择

文章目录 框架版本介绍和编程语言选择 一、框架版本介绍 二、编程语言选择 框架版本介绍和编程语言选择

pandas学习

(个人学习使用) 添加索引 # index是行索引&#xff0c;columns是列索引 pd.DataFrame(score, indexidx, columnscol) 常用属性和方法 data.shape # 形状 data.index # 行索引 data.columns # 列索引 data.values # 里面的值&#xff0c;结果是ndarray类型数组 …

uniapp 的input组件在@input事件中限制用户可输入数值的范围,出现视图不更新的bug。

在input事件拿到用户输入的值&#xff0c;然后给input组件绑定的值赋值之前&#xff0c;判断用户输入的不能超过最大值&#xff0c;超过的话默认为100&#xff0c;&#xff0c;这个判断和赋值然后视图更新只能触发一次&#xff0c;之后在输入&#xff0c;发现值改了页面但是不更…

关于网络通信安全协议的一些知识(ssl,tls,CA,https)

首先了解一下http协议的变迁。 http1.0默认短连接&#xff0c;1.1默认长连接并且可以管道传输&#xff0c;但是存在队头阻塞问题&#xff1b; https就是在tcp和http之间加了SSL/TLS层。 http2也是安全的&#xff0c;改进是hpack二进制和编码压缩减小体积&#xff0c;stream没有…

微信存储空间清理

1、打开电脑版微信、点击左下角的三根横线 2、点击左侧的“设置” 3、弹出层左侧点击“文件管理” 4、点击右下角“打开文件夹” 5、默认4打开的为当前登录账户对应的文件夹&#xff08;建议清理路径步骤7&#xff09; 6、点击“WeChat Files”查看其他账户的文件夹&#xff0c…

智能制造RFID设备包括哪些?

智能制造是现代制造业的重要发展方向&#xff0c;其核心是数字化、网络化和智能化。而在智能制造中&#xff0c;RFID设备是一种不可或缺的技术手段&#xff0c;主要用于实现物品的识别、追踪和化管理。以下是智能制造中常用的RFID设备及其功能&#xff1a; 1、 RFID读写器 RFID…

SpringBoot整合Druid

SpringBoot整合Druid POM.xml <!-- 整合Druid --> <dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.10</version> </dependency> <!--自启动Druid管理后台--> <depe…

[JAVAee]synchronized关键字

目录 1.synchronized的特性 ①互斥性 ②可重入性 2.synchronized的使用示例 ①修饰普通方法 ②修饰静态方法 ③修饰代码块 1.synchronized的特性 ①互斥性 互斥性,就像是给门上锁了一样. 当A线程使用了被synchronized修饰的代码块并对其上锁,其他线程(B线程,C线程)想要使…

Hadoop 之 Centos 7 搭建 Zookeeper 3.8.2 集群(六)

Zookeeper 集群搭建 一.Centos 虚拟机配置1.新建虚拟机&#xff0c;选择【典型】2.下一步&#xff0c;【稍后安装操作系统】3.下一步&#xff0c;选择【Linux】【Centos7 64位】4.下一步&#xff0c;设置虚拟机名称和安装目录5.下一步&#xff0c;默认6.下一步&#xff0c;【自…

计算机系统结构-多处理机

概念&#xff0c;多处理机指的是&#xff0c;多台含cpu的机器共享一个存储器。 &#xff08;可以通过网络宽带&#xff0c;也可以通过线直连这个存储器。当然他们也可以有自己的私有存储器或者高速缓存&#xff09; 几个cpu公用一个总线&#xff0c;没问题。但是如果十几个cpu…

elasticsearch查询操作(API方式)

说明&#xff1a;elasticsearch查询操作除了使用DSL语句的方式&#xff08;参考&#xff1a;http://t.csdn.cn/k7IGL&#xff09;&#xff0c;也可以使用API的方式。 准备 使用前需先导入依赖 <!--RestHighLevelClient依赖--><dependency><groupId>org.ela…

pytest中生成allure报告时,测试报告中统计的用例数不正确

【问题描述】&#xff1a;pytest中生成allure报告时&#xff0c;测试报告中统计的用例数不正确&#xff0c;用例数总是比实际用例数多 【问题定位】&#xff1a;因为生成index.html的allure报告&#xff0c;是根据临时的json文件生成的。每次运行时&#xff0c;没有删除旧的js…

php使用chatGPT生成一些东西做一个记录

好久没写了&#xff0c;这么长时间都去坐一些自己感兴趣的事情去了。 之前使用chatgpt-3,效果一直不咋好&#xff0c;这里我们来说说各个版本区别 gpt-3收费成本可以接受&#xff0c;生成的内容对话有点不太聪明的样子 git-3.5-turbo收费相对来说低&#xff0c;生成文本质量…

PCB封装设计指导(十四)保存并产生device文件

PCB封装设计指导(十四)保存并产生PSM和device文件 封装命名完成之后,基本上封装的建立也接近结束,如果调网表使用的是第三方网表,还需要device文件才能调入。 但是PSM文件是不管第一方和第三方网表都是需要的,下面介绍如何产生这两个文件。 打开封装文件,如下图点击Fil…

openlayers系列:加载arcgis和geoserver在线离线切片

https://www.freesion.com/article/1751396517/ 1.背景 有个项目需要使用openlayer加载各种服务上发布的数据&#xff0c;坐标系也不同&#xff0c;我们都知道openalyer默认可以加载EPAG:3857,要加载4490的坐标系的数据需要重新定义一下&#xff0c;之后再加载。一想起要重新…