Elasticsearch(十五)搜索---搜索匹配功能⑥--基于地理位置查询

一、前言

随着互联网+的热门,越来越多的传统行业将全部或者部分业务转移到互联网上,其中不乏一些和地理位置强相关的行业。基于地理位置的搜索功能,大大提升了人们的生活和工作效率。例如,外出旅行时,只需要用手机打开订酒店的应用软件,查找附近心仪的酒店下单即可;又或者打车行业,人们不用在寒冷的户外拦截出租车,只需要在室内打开打车APP定位到当前位置,然后确定目的地,系统就可以为附近的车辆派发订单。
幸运的是,ES为用户提供了基于地理位置的搜索功能。它主要支持两种类型的地理查询:一种是地理点(geo_point),即经纬度查询,另一种是地理形状查询(geo_shape),即支持点、线、圆形和多边形查询等。
从实用性来说,地理点(即geo_point)数据类型的使用的更多一些,本节也只对地理点类型进行介绍。
对应于geo_point字段类型的查询方式有3种,分别为geo_distance查询、geo_bounding_box查询和geo_polygon
为了更方便专一的学习地理搜索,我们在hotel索引批量增加多条文档,内容如下:

POST /hotel_location/_doc/_bulk
{"index":{"_id":51}}
{"title":"连锁酒店1","location":{"lat":"40.17836693398477","lon":"116.64002551005981"}}
{"index":{"_id":52}}
{"title":"连锁酒店2","location":{"lat":"40.19103839805197","lon":"116.5624013764374"}}
{"index":{"_id":53}}
{"title":"连锁酒店3","location":{"lat":"40.13933715136454","lon":"116.63441990026217"}}
{"index":{"_id":54}}
{"title":"连锁酒店4","location":{"lat":"40.14901664712196","lon":"116.53067995860928"}}
{"index":{"_id":55}}
{"title":"连锁酒店5","location":{"lat":"40.125057718315716","lon":"116.62963567059545"}}
{"index":{"_id":56}}
{"title":"连锁酒店6","location":{"lat":"40.19216257806647","lon":"116.64025980109571"}}
{"index":{"_id":57}}
{"title":"连锁酒店7","location":{"lat":"40.16371689899584","lon":"116.63095084701624"}}
{"index":{"_id":58}}
{"title":"连锁酒店8","location":{"lat":"40.146045218040605","lon":"116.5696251832195"}}
{"index":{"_id":59}}
{"title":"连锁酒店9","location":{"lat":"40.144735806234166","lon":"116.60712460957835"}}

通过上面的步骤,我们完成了9条经纬度数据的插入;可以通过search语句查询一下结果

二、geo_bounding_box

geo_bounding_box语法又称为地理坐标盒模型,在当前语法中,只需选择一个矩阵范围(输入矩阵的左上角的顶点地理坐标和矩阵的右上角的顶点地理坐标,构建成为一个矩阵),即可计算出当前矩阵中符合条件的元素;
![在这里插入图片描述](https://img-blog.csdnimg.cn/d1e52cc370f94779b7261c47c0974bba.png

简单来说呢,就是给定两个坐标,通过这两个坐标形成对角线,平行于地球经纬度从而得到的一个矩阵。采用geo_bounding_box语法可以得到坐落于当前矩阵中的元素的信息;
假设如上图我这边给定两个坐标,分别是A(116.498353,40.187328) 和 B(116.610461,40.084509),这样我们就得到了一个矩阵。
ES的geo_bounding_box语法有很多种查询方式,但是需要注意的是我们要确定好哪个是左上角的坐标,哪个是右下角的坐标,并且这两个坐标不能互换
那上面的例子通过A\B两点去找矩阵范围内的酒店,DSL如下

GET /hotel/_search
{
  "query": {
    "geo_bounding_box": {
      "location": {
        "top_left": {
          "lat": 40.187328,
          "lon": 116.498353
        },
        "bottom_right": {
          "lat": 40.084509,
          "lon": 116.610461
        }
      }
    }
  }
}

输出如下,可以看到有3家酒店位于这个矩阵范围中:

{
   ...
  "hits" : {
    "total" : {
      "value" : 3,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "hotel",
        "_type" : "_doc",
        "_id" : "54",
        "_score" : 1.0,
        "_source" : {
          "title" : "连锁酒店4",
          "location" : {
            "lon" : "116.53067995860928",
            "lat" : "40.14901664712196"
          }
        }
      },
      {
        "_index" : "hotel",
        "_type" : "_doc",
        "_id" : "58",
        "_score" : 1.0,
        "_source" : {
          "title" : "连锁酒店8",
          "location" : {
            "lon" : "116.5696251832195",
            "lat" : "40.146045218040605"
          }
        }
      },
      {
        "_index" : "hotel",
        "_type" : "_doc",
        "_id" : "59",
        "_score" : 1.0,
        "_source" : {
          "title" : "连锁酒店9",
          "location" : {
            "lon" : "116.60712460957835",
            "lat" : "40.144735806234166"
          }
        }
      }
    ]
  }
}

那么除了上面的查询的DSL语法之外,还有如下语法,获取的结果和上面DSL均相同:

  1. 基于经纬度数组的DSL语法

需要注意的是,数组形式的,经纬度顺序需调换一下
这样就不需要输入lat和lon了,直接通过数组表示经纬度

GET /hotel/_search
{
  "query": {
    "geo_bounding_box": {
      "location": {
        "top_left": [116.498353,40.187328],
        "bottom_right": [116.610461,40.084509]
      }
    }
  }
}
  1. 基于经纬度字符串的DSL语法

字符串不同于数组,经纬度顺序不需要调换

GET /hotel/_search
{
  "query": {
    "geo_bounding_box": {
      "location": {
        "top_left": "40.187328,116.498353",
        "bottom_right": "40.084509,116.610461"
      }
    }
  }
}
  1. 基于经纬度边界框WKT的DSL语法

可以看到先是A的经度,再是B的经度,然后是A的纬度,再是B的纬度,通过BBOX封装

GET /hotel/_search
{
  "query": {
    "geo_bounding_box": {
      "location": {
         "wkt": "BBOX (116.498353,116.610461,40.187328,40.084509)"
      }
    }
  }
}
  1. 基于经纬度GeoHash的DSL语法
//关于GeoHash可以参考两个网址
// 全球GeoHash地图 http://geohash.gofreerange.com/
// GeoHash坐标在线转换 http://geohash.co/
GET /hotel/_search
{
  "query": {
    "geo_bounding_box": {
      "location": {
        "top_left": "wx4udgz",
        "bottom_right": "wx4uj91"
      }
    }
  }
}
  1. 基于经纬度顶点属性的DSL语法
    相当于把top_left和bottom_right分成了四个
GET /hotel/_search
{
  "query": {
    "geo_bounding_box": {
      "location": {
        "top": 40.187328,
        "left": 116.498353,
        "bottom": 40.084509,
        "right": 116.610461
      }
    }
  }
}

至此,采用上述6种方式计算的矩阵坐落元素所执行结果均一致,且逐个在地图上核实,所召回的建筑均真实的在上图的矩阵中;
计算某个矩阵或者是多边形中的元素,在Redis中目前是不支持的,在这方面ES表现的更为强大;通过上述的三种语法可以看到,ES可以很好的支持 矩阵、圆、多边形的空间地理检索,通过查看Redis的语法可以看到Redis目前只支持圆的空间地理检索

在java客户端使用new GeoBoundingBoxQueryBuilder()构造geo_bounding_box请求,我们可以看到它有很多附加的方法
在这里插入图片描述
但是我们这次使用的主要是setCorners()设置两点距离,可以看到它支持geoHash,两点坐标,经纬度顶点等查询方法。
在这里插入图片描述
我们使用两点坐标来进行查询
Service如下:

public List<Hotel> geoBoundingBoxQuery(HotelDocRequest hotelDocRequest) throws IOException {
		//新建搜索请求
		String indexName = getNotNullIndexName(hotelDocRequest);
		SearchRequest searchRequest = new SearchRequest(indexName);
		SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
		// 构造左上点坐标
		GeoPoint topLeft = new GeoPoint(40.187328D, 116.498353D);
		// 构造右下点坐标
		GeoPoint bottomRight = new GeoPoint(40.084509D, 116.610461D);
		GeoBoundingBoxQueryBuilder geoBoundingBoxQueryBuilder = new GeoBoundingBoxQueryBuilder("location")
				.setCorners(topLeft,bottomRight);
		searchSourceBuilder.query(geoBoundingBoxQueryBuilder);
		searchRequest.source(searchSourceBuilder);
		return getQueryResult(searchRequest);
	}

这次getQueryResult(),我们需要将结果能够正常返回location这个属性
首先我们建立Location这个类,lat代表纬度,lon代表经度:

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Location {
	private String lat;
	private String lon;
}

然后在hotel索引类增加location属性,这个属性名和你建造索引用的是一样的:
在这里插入图片描述
然后对getQueryResult进行改造,因为我们获取到location的json对象其实是类似于hashMap结构的对象,我们可以使用JSONUtil.toJsonStr(location)将其先转化为json字符串,然后通过JSONUtil.toBean(String jsonString,T)转化成我们的目标对象Location。
完整代码如下:

private List<Hotel> getQueryResult(SearchRequest searchRequest) throws IOException {
		ArrayList<Hotel> resultList = new ArrayList<>();
		SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
		RestStatus status = searchResponse.status();
		if (status != RestStatus.OK) {
			return Collections.emptyList();
		}
		SearchHits searchHits = searchResponse.getHits();
		for (SearchHit searchHit : searchHits) {
			Hotel hotelResult = new Hotel();
			hotelResult.setId(searchHit.getId());   //文档_id
			hotelResult.setIndex(searchHit.getIndex());   //索引名称
			hotelResult.setScore(searchHit.getScore());   //文档得分
			//转换为Map
			Map<String, Object> dataMap = searchHit.getSourceAsMap();
			hotelResult.setTitle((String) dataMap.get("title"));
			hotelResult.setCity((String) dataMap.get("city"));
			Object price = dataMap.get("price");
			if (price != null) {
				hotelResult.setPrice(Double.valueOf((String) price));

			}
			//获取location
			Object location = dataMap.get("location");
			if (location != null) {
				hotelResult.setLocation(JSONUtil.toBean(JSONUtil.toJsonStr(location), Location.class));
			}
			resultList.add(hotelResult);
		}
		return resultList;
	}

然后回到controller调用:

	@PostMapping("/query/bounding-box")
	public FoundationResponse<List<Hotel>> geoBoundingBoxQuery(@RequestBody HotelDocRequest hotelDocRequest) {
		try {
			List<Hotel> hotelList = esQueryService.geoBoundingBoxQuery(hotelDocRequest);
			if (CollUtil.isNotEmpty(hotelList)) {
				return FoundationResponse.success(hotelList);
			} else {
				return FoundationResponse.error(100,"no data");
			}
		} catch (IOException e) {
			log.warn("搜索发生异常,原因为:{}", e.getMessage());
			return FoundationResponse.error(100, e.getMessage());
		} catch (Exception e) {
			log.error("服务发生异常,原因为:{}", e.getMessage());
			return FoundationResponse.error(100, e.getMessage());
		}
	}

postman调用截图:
在这里插入图片描述

三、geo_distance

ES中的geo_distance语法与Redis中的georadius语法类似,通过给定一个坐标和半径,圈出圆内的点。在ES可以定义一些排序规则返回召回结果集数据与当前坐标的距离,Redis中默认返回了距离;
与geo_bounding_box语法类似,geo_distance语法也有多种查询方式,如 经纬度属性、经纬度数组、经纬度字符串、GeoHash等,下面就简单的以 经纬度字符串为例进行演示,重新选定坐标,以纬度(116.5864,40.174697)为例,查询3km范围内的酒店

GET /hotel/_search
{
  "query": {
    "geo_distance":{
      "distance":"3km",
      "location":"40.174697,116.5864"
    }
  }
}

查询结果如下:

{
  ...
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "hotel",
        "_type" : "_doc",
        "_id" : "52",
        "_score" : 1.0,
        "_source" : {
          "title" : "连锁酒店2",
          "location" : "40.19103839805197,116.5624013764374"
        }
      }
    ]
  }
}

而geo_distance和后面我们的sort排序用的很紧密,例如微信附近的人就可以通过该功能实现,其中结合sort可以返回当前位置与目标位置之间的距离。这个我们后面会介绍。
在Java客户端可以使用new GeoDistanceQueryBuilder()构造geo_distance查询,通过distance()设置以指定坐标点为中心的半径大小以及距离的单位,point()设置指定坐标点,service如下:

	public List<Hotel> geoDistanceQuery(HotelDocRequest hotelDocRequest) throws IOException {
		//新建搜索请求
		String indexName = getNotNullIndexName(hotelDocRequest);
		SearchRequest searchRequest = new SearchRequest(indexName);
		SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
		// 假设目标距离坐标
		GeoPoint sourcePoint = new GeoPoint(40.174697D, 116.5864D);
		GeoDistanceQueryBuilder geoDistanceQueryBuilder = new GeoDistanceQueryBuilder("location")
				.distance("3", DistanceUnit.KILOMETERS).point(sourcePoint);
		searchSourceBuilder.query(geoDistanceQueryBuilder);
		searchRequest.source(searchSourceBuilder);
		return getQueryResult(searchRequest);
	}

controller如下:

	@PostMapping("/query/geo-distance")
	public FoundationResponse<List<Hotel>> geoDistanceQuery(@RequestBody HotelDocRequest hotelDocRequest) {
		try {
			List<Hotel> hotelList = esQueryService.geoDistanceQuery(hotelDocRequest);
			if (CollUtil.isNotEmpty(hotelList)) {
				return FoundationResponse.success(hotelList);
			} else {
				return FoundationResponse.error(100, "no data");
			}
		} catch (IOException e) {
			log.warn("搜索发生异常,原因为:{}", e.getMessage());
			return FoundationResponse.error(100, e.getMessage());
		} catch (Exception e) {
			log.error("服务发生异常,原因为:{}", e.getMessage());
			return FoundationResponse.error(100, e.getMessage());
		}
	}

postman截图如下:
在这里插入图片描述

四、geo_polygon

ES的geo_polygon语法,可以通过指定多个坐标点,从而构成一个多边形,然后从当前多边形中召回坐落其中的元素进行召回;在当前语法中,最少需要3个坐标,从而构成一个多边形;
例如我在ES增加一个我公司的坐标(121.530533,31.085692)
在这里插入图片描述

POST /hotel/_doc/031
{
  "title":"上海闵行浦江智谷","location":{"lat":"31.085692","lon":"121.530533"}
}

然后可以指定3个坐标,将公司位置坐落于这三个坐标中,看看公司位置是否可以检索出来,3个坐标在地图上的展示如下

坐标点A:(121.531257,31.085262)
坐标点B:(121.529694,31.085494)
坐标点C:(121.530632,31.086252)
在这里插入图片描述

ES的geo_polygon语法也支持多种语法,如 经纬度数组、经纬度字符串、GeoHash值等;这里就采用字符串演示了,另外两种语法不再赘述;其执行DSL如下

GET /hotel/_search
{
  "query": {
    "geo_polygon": {
      "location": {
        "points": [
          "31.085262,121.531257",
          "31.085494, 121.529694",
          "31.086252, 121.530632"
        ]
      }
    }
  }
}

结果如下:

{
   ...
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "hotel",
        "_type" : "_doc",
        "_id" : "031",
        "_score" : 1.0,
        "_source" : {
          "title" : "上海闵行浦江智谷",
          "location" : {
            "lat" : "31.085692",
            "lon" : "121.530533"
          }
        }
      }
    ]
  }
}

可以看到,通过三个点构建成一个三角形,当目标元素坐落于所构建的形状中,即可很好的将其召回;

到这里,关于ES的geo_point语法已经接近尾声了,简单的了解了一下ES的空间地理支持;下面再新增一种相对复杂一点的地形,看看geo_polygon语法可以很好的支持不。

通过刚才的3个坐标,我们新增一个坐标,构建一个凹形的多边形,将目标节点剔除在多边形外,看看ES在这方面的支持如何,最终构建的多边形如下:
在这里插入图片描述

GET /hotel/_search
{
  "query": {
    "geo_polygon": {
      "location": {
        "points": [
          "31.085262,121.531257",
          "31.086252, 121.530632",
          "31.085494, 121.529694",
          "31.085854,121.530524"
        ]
      }
    }
  }
}

结果没有找到:
在这里插入图片描述
需要注意的是,查询语句输入的点的顺序是需要注意的,它会按照你输入的点的顺序构成不同的多边形,从而出现不同的结果,就像我上图那样的使用箭头标注顺序,如果我改变某个点的顺序,有可能就会把目标囊括进去从而和之前结果不一样。
那么在java客户端可以使用new GeoPolygonQueryBuilder ()构造geo_polygon查询,构造方法包含需要查询的字段以及可以接收一个GeoPoint数组,数组就和我们刚才DSL中输入的那些点是一样的,记住要按照顺序放,service如下:

public List<Hotel> geoPolygonQuery(HotelDocRequest hotelDocRequest) throws IOException {
		//新建搜索请求
		String indexName = getNotNullIndexName(hotelDocRequest);
		SearchRequest searchRequest = new SearchRequest(indexName);
		SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
		// 假设目标距离坐标
		ArrayList<GeoPoint> geoPoints = new ArrayList<GeoPoint>();
		GeoPoint sourcePoint1 = new GeoPoint(31.085262D, 121.531257D);
		GeoPoint sourcePoint2 = new GeoPoint(31.085494D, 121.529694D);
		GeoPoint sourcePoint3 = new GeoPoint(31.086252D, 121.530632D);
		geoPoints.add(sourcePoint1);
		geoPoints.add(sourcePoint2);
		geoPoints.add(sourcePoint3);
		GeoPolygonQueryBuilder geoPolygonQueryBuilder = new GeoPolygonQueryBuilder("location", geoPoints);
		searchSourceBuilder.query(geoPolygonQueryBuilder);
		searchRequest.source(searchSourceBuilder);
		return getQueryResult(searchRequest);
	}

controller如下:

@PostMapping("/query/geo-polygon")
	public FoundationResponse<List<Hotel>> geoPolygonQuery(@RequestBody HotelDocRequest hotelDocRequest) {
		try {
			List<Hotel> hotelList = esQueryService.geoPolygonQuery(hotelDocRequest);
			if (CollUtil.isNotEmpty(hotelList)) {
				return FoundationResponse.success(hotelList);
			} else {
				return FoundationResponse.error(100, "no data");
			}
		} catch (IOException e) {
			log.warn("搜索发生异常,原因为:{}", e.getMessage());
			return FoundationResponse.error(100, e.getMessage());
		} catch (Exception e) {
			log.error("服务发生异常,原因为:{}", e.getMessage());
			return FoundationResponse.error(100, e.getMessage());
		}
	}

postman执行如下:
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

NineData:高效、安全、可靠的DB2数据管理平台

Db2 是老牌厂商 IBM 研发和维护的关系型数据库管理系统。作为一个拥有悠久历史的数据库系统&#xff0c;Db2 凭借它的高可靠、可扩展和高安全性等诸多优点&#xff0c;在如今的数据库市场依然占据相当大的份额。 对于诸多金融行业的企业而言&#xff0c; Db2 作为承载其核心业务…

R语言的物种气候生态位动态量化与分布特征模拟实践技术

在全球气候快速变化的背景下&#xff0c;理解并预测生物种群如何应对气候变化&#xff0c;特别是它们的地理分布如何变化&#xff0c;已经变得至关重要。利用R语言进行物种气候生态位动态量化与分布特征模拟&#xff0c;不仅可以量化描述物种对环境的需求和适应性&#xff0c;预…

【开源】基于SpringBoot的农村物流配送系统的设计和实现

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 系统登录、注册界面2.2 系统功能2.2.1 快递信息管理&#xff1a;2.2.2 位置信息管理&#xff1a;2.2.3 配送人员分配&#xff1a;2.2.4 路线规划&#xff1a;2.2.5 个人中心&#xff1a;2.2.6 退换快递处理&#xff1a;…

Windows server部署filebeat到kafka

需求&#xff1a;Windows dhcp日志需要实时传输到elk或者其他告警平台。 1、filebeat下载地址&#xff1a;https://www.elastic.co/cn/downloads/beats/filebeat 2、下载后解压后配置filebeat.yml文件&#xff0c; 3、README.md文件中有运行的操作方法&#xff1a;cmd上进入f…

手写效果流式响应(langchain+fastapi+js)

这是一个前后端完整可用的小项目 后端是 Python 的 FastAPI 框架&#xff0c;调用 langchain 进行 openai 的模型对话。前端是纯html css javascript&#xff0c;没调用任何第三方库&#xff0c;方便集成到 Vue React 等现有前端项目。 聊天界面&#xff1a; 效果就是提问之…

【开源】基于SpringBoot的高校学院网站的设计和实现

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 学院院系模块2.2 竞赛报名模块2.3 教育教学模块2.4 招生就业模块2.5 实时信息模块 三、系统设计3.1 用例设计3.2 数据库设计3.2.1 学院院系表3.2.2 竞赛报名表3.2.3 教育教学表3.2.4 招生就业表3.2.5 实时信息表 四、系…

ERP管理系统的运作流程是怎样的?

ERP管理系统的运作流程是怎样的&#xff1f; 下文中用到的图片和系统&#xff0c;都来自于我们公司正在用的软件——简道云进销存软件 这是我们团队搭建的模板&#xff0c;需要的可以自取&#xff0c;可以直接使用&#xff0c;也可以在模板的基础上自行搭建 ERP管理系统模板&…

分布式日志和链路追踪

分布式日志 实现思路 分布式日志框架服务的实现思路基本是一致的&#xff0c;如下&#xff1a; 日志收集器&#xff1a;微服务中引入日志客户端&#xff0c;将记录的日志发送到日志服务端的收集器&#xff0c;然后以某种方式存储数据存储&#xff1a;一般使用ElasticSearch分…

【QT开发笔记-基础篇】| 第四章 事件QEvent | 4.10 总结QT中的事件传递流程

本节对应的视频讲解&#xff1a;B_站_链_接 【QT开发笔记-基础篇】 第4章 事件 4.10 总结事件传递流程(1) 事件处理函数接受还是忽略 本章要实现的整体效果如下&#xff1a; 事件传递总流程图&#xff0c;如下&#xff1a; 这张图是不是还不太明白&#xff1f;&#xff1f; 别…

RabbitMQ基础篇 笔记

RabbitMQ 余额支付 同步调用 一步一步的来&#xff0c;支付业务写完后&#xff0c;如果之后加需求&#xff0c;还需要增加代码&#xff0c;不符合开闭原则。 性能上也有问题&#xff0c;openfeign是同步调用&#xff0c;性能太差。 同步调用耦合太多。 同步的优势是可以立…

matlab中narginchk函数用法及其举例

matlab中narginchk函数用法及其举例 narginchk在编写子函数程序时候&#xff0c;在验证输入参数数目方面具有重要作用&#xff0c;本博文讲一讲该函数的用法。 一、narginchk功能 narginchk的作用是验证输入参数数目。 二、语法 narginchk(minArgs,maxArgs)narginchk(minA…

windows服务器环境下使用php调用com组件

Office设置 安装 office2013 且通过正版激活码激活 在组件服务 计算机 我的电脑 DOM 中找到 Microsoft Word 97 - 2003 文档 服务&#xff0c;右键属性 身份验证调整为 无 在 标识中 调整为 交互式用户 php环境设置 开启com组件扩展 在php.ini中设置 extensionphp_com_dotn…

同范围中的嵌入式和单片机区别是什么?

今日话题&#xff0c;同范围中的嵌入式和单片机区别是什么&#xff1f;嵌入式系统和单片机不仅仅是软硬件的区别&#xff0c;更涉及到应用领域和功能特性的不同。嵌入式系统通常包括一个完整的计算机系统&#xff0c;其中包括处理器、内存、输入输出接口以及一个操作系统&#…

2023年中国消防报警设备市场规模现状及行业竞争趋势分析[图]

消防安全行业主要分为消防产品和消防工程两个子行业。消防产品又可细分成消防装备、消防报警、自动灭火、防火与疏散、通用与防烟排烟、消防供水等 6 大类&#xff0c;其中消防装备主要用于消防部队&#xff0c;其他 5 大类主要用于建筑物消防。 消防行业内容 资料来源&#x…

21款奔驰GLE450升级23P驾驶辅助 缓解开车疲劳

驾驶辅助和自动驾驶的区别就是需要人为去接管&#xff0c;虽然车辆会根据道路自己行驶&#xff0c;弯道上也能居中自动修正行驶&#xff0c;长时间不接管方向盘&#xff0c;系统会提示人为接管&#xff0c;这就是奔驰的23P驾驶辅助系统&#xff0c; 很多车友升级23P驾驶辅助系…

苍穹外卖-01

苍穹外卖-01 课程内容 软件开发整体介绍苍穹外卖项目介绍开发环境搭建导入接口文档Swagger 项目整体效果展示&#xff1a; ​ 管理端-外卖商家使用 ​ 用户端-点餐用户使用 当我们完成该项目的学习&#xff0c;可以培养以下能力&#xff1a; 1. 软件开发整体介绍 作为一名…

用Visual Studio(VS)开发UNIX/Linux项目

目录 FTP是免不了的 正确设置头文件 组织项目结构 创建何种项目类型 FTP自动上传 大部分具有Windows开发经验的程序员会比较喜欢使用Visual Studio&#xff0c;而大部分Unix/Linux程序员则喜欢使用UltraEdit直接在主机上写代码。 为什么直接在主机上写代码呢&#xff0c;因…

Leo赠书活动-03期 【ChatGPT 驱动软件开发:AI 在软件研发全流程中的革新与实践 】

✅作者简介&#xff1a;大家好&#xff0c;我是Leo&#xff0c;热爱Java后端开发者&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f34e;个人主页&#xff1a;Leo的博客 &#x1f49e;当前专栏&#xff1a; 赠书活动专栏 ✨特色专栏&#xff1a;…

【JAVA学习笔记】45 - (35 - 43)第十章作业

项目代码 https://github.com/yinhai1114/Java_Learning_Code/tree/main/IDEA_Chapter10/src/com/yinhai/homework10 1.静态属性的共享性质 判断下列输出什么 public class HomeWork01 {public static void main(String[] args) {Car c new Car();//无参构造时改变color为red…

Redis快速上手篇(四)(Spring Cache,缓存配置)(注解方式)

Spring Cache 从3.1开始&#xff0c;Spring引入了对Cache的支持。其使用方法和原理都类似于Spring对事务管理的支持。Spring Cache是作用在方法上的 使用Spring Cache的时候我们要保证我们缓存的方法对于相同的方法参数要有相同的返回结果。 使用Spring Cache需要我们做两方面…