在Java中使用GeoTools解析POI数据并存储到PostGIS实战

news2024/11/17 0:02:33

目录

前言

一、POI数据相关介绍

1、原始数据说明

2、空间数据库表设计

二、POI数据存储的设计与实现

1、对应的数据模型对象的设计

2、属性表数据和空间信息的读取

3、实际运行结果

三、总结


前言

        POI点,全称为Point of Interest(兴趣点),是指一切被抽象为点要素的空间地理实体,尤其是与人们生活密切相关的地理要素,如小区、餐馆、商场、车站等。POI是地理信息数据的一部分,用于丰富地图内容,提供用户所需的地理信息,POI数据在地图应用、导航系统、位置服务、市场营销、城市规划等领域有着广泛应用。

        POI数据涵盖了丰富的地理信息和属性特征,能够反映出一个地区的商业、文化、交通等各方面的特色,每个POI点主要包含四方面的信息:名称、类别、坐标、分类。(1)名称:POI的名称或标题。(2)地址:POI的详细地址。(3)经纬度坐标:POI的地理位置坐标,通过经纬度坐标将现实世界中的地点与数字世界进行关联。(4)分类:POI所属的类别。(5)描述:关于POI的详细描述。(6)联系方式:如电话、网址等。(7)附加信息:如营业时间、用户评价、图片等。可以看出,POI数据的核心要素在于其地理位置信息和属性描述。POI数据的特点有多样性(涵盖多种类型的地理实体)、动态性(POI信息会随时间变化而更新)、空间性(具有明确的地理位置)、可查询性(用户可以根据需求检索特定的POI)。

        因此,可以看出,学会管理并正确的使用POI数据,对于我们进行城市规划、导航服务、位置服务、智慧旅游、智慧应急等方面有重要的应用。在我们应用这些数据之前,需要先将POI数据管理起来。本文即在这样的场景下产生。与GDAL的shp数据处理方式不同,在GeoTools中的处理方法有一定的不同。文章分享的方法可以在分布式环境中利用Mybatis-Plus这种ORM框架进行快速的空间数据批量插入。与GeoTools官方提供的PostGIS数据读写相比,本文分享的方法将更加方便,易于与其它项目进行集成。

一、POI数据相关介绍

        在讲解如何利用GeoTools进行数据管理时,我们先对POI数据进行简单的说明。POI数据不仅包含丰富的空间位置信息,同时包含很丰富的分类,以餐饮类的POI为例,我们可以分为大类、中类和小类。

1、原始数据说明

按照大类分为餐饮服务、中类分为中餐厅、西餐、烧烤,小类可以分为湘菜、川菜等。

        在这里,从行政区划上,我们把POI按照其归属进行了划分。在后续的统计中可以充分的利用这些数据。 这里的数据也是从互联网上抓取的数据,大多数的POI数据都是有标准的大类、中类、小类。当然,在拿到的部分POI数据,比如商业住宅的数据,

        在商务住宅的POI数据中,所有的分类数据都集中到了大类这个字段,而另外两个字段比如中类和小类则是空的。其它的数据都是正确的,因此我们需要对商务住宅这个大类的数据进行简单的分拆。然后对应到具体的大类、中类、小类上面。 

2、空间数据库表设计

        在上一篇博客中,我们对POI信息表的空间数据属性字段有了具体的了解。与Shapefile等空间数据表相同,在PostGIS空间数据库中,我们也需要设计对应的空间表来存储对应的空间数据。

        以上就是Shapefile中的属性字段信息,按照一一映射的原则,我们在PostGIS当中也同样的来设计对应的空间表。 

        大家可以使用自己熟悉的工具来进行表结构的设计,然后在数据库客户端软件中进行创建表结构即可。这里同样将数据表的表结构贴出来,供大家参考:

CREATE TABLE "public"."biz_poi_info" (
  "pk_id" int8 NOT NULL,
  "name" varchar(255) COLLATE "pg_catalog"."default",
  "main_category" varchar(255) COLLATE "pg_catalog"."default",
  "type" varchar(255) COLLATE "pg_catalog"."default",
  "subtype" varchar(255) COLLATE "pg_catalog"."default",
  "address" varchar(255) COLLATE "pg_catalog"."default",
  "province_name" varchar(255) COLLATE "pg_catalog"."default",
  "city_name" varchar(255) COLLATE "pg_catalog"."default",
  "area_name" varchar(255) COLLATE "pg_catalog"."default",
  "lon_wgs84" numeric(18,11),
  "lat_wgs84" numeric(18,11),
  "geom" "public"."geometry",
  "year" int4,
  "create_by" int8,
  "create_time" timestamp(6),
  "update_by" int8,
  "update_time" timestamp(6),
  CONSTRAINT "pk_biz_poi_info" PRIMARY KEY ("pk_id")
);

CREATE INDEX "idx_biz_poi_info_geom" ON "public"."biz_poi_info" USING gist (
  "geom" "public"."gist_geometry_ops_2d"
);
COMMENT ON COLUMN "public"."biz_poi_info"."pk_id" IS 'pk_id';
COMMENT ON COLUMN "public"."biz_poi_info"."name" IS '名称';
COMMENT ON COLUMN "public"."biz_poi_info"."main_category" IS '大类,比如:餐饮服务';
COMMENT ON COLUMN "public"."biz_poi_info"."type" IS '中类,比如:中餐';
COMMENT ON COLUMN "public"."biz_poi_info"."subtype" IS '小类';
COMMENT ON COLUMN "public"."biz_poi_info"."address" IS '地址';
COMMENT ON COLUMN "public"."biz_poi_info"."province_name" IS '省';
COMMENT ON COLUMN "public"."biz_poi_info"."city_name" IS '市';
COMMENT ON COLUMN "public"."biz_poi_info"."area_name" IS '区';
COMMENT ON COLUMN "public"."biz_poi_info"."year" IS '年份';
COMMENT ON COLUMN "public"."biz_poi_info"."create_by" IS '创建人';
COMMENT ON COLUMN "public"."biz_poi_info"."create_time" IS '创建时间';
COMMENT ON COLUMN "public"."biz_poi_info"."update_by" IS '更新人';
COMMENT ON COLUMN "public"."biz_poi_info"."update_time" IS '更新时间';
COMMENT ON TABLE "public"."biz_poi_info" IS '保存兴趣点信息表';

        以上就是对POI数据进行简单的介绍,以及对POI数据的时空数据表的物理模型和表结构进行了讲解。请注意,在进行空间数据库设计的时候,请务必安装PostGIS的扩展,否则上面的SQL将无法运行,为了在后面的空间分析和查询的服务中发挥出更好的性能,我们给Geometry字段建立空间索引。

二、POI数据存储的设计与实现

        在介绍完POI的属性表结构和空间数据库物理表模型之后,我们来具体讲解如何使用GeoTools来进行属性的读取,并调用Mybatis-Plus的批量入库代码,将2020年星城长沙的POI数据进行入库操作。

1、对应的数据模型对象的设计

        众所周知,在面向对象的设计中,我们需要给数据库模型的表设计一个对应的实体类,这里简称为实体类。一般字段与数据库物理表是一一对应的。这里我们直接给出原始的代码:

package com.yelang.project.extend.earthquake.domain;
import java.io.Serializable;
import java.math.BigDecimal;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.yelang.framework.handler.PgGeometryTypeHandler;
import com.yelang.framework.web.domain.BaseEntity;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
@TableName(value = "biz_poi_info", autoResultMap = true)
@NoArgsConstructor
@AllArgsConstructor
@Setter
@Getter
@ToString
/**
 * - 兴趣点信息表实体类
 * @author 夜郎king
 *
 */
public class PoiInfo extends BaseEntity implements Serializable {
	private static final long serialVersionUID = -9163178655131959272L;
	@TableId(value = "pk_id")
	private Long pkId;// 主键
	private String name;// 名称
	@TableField(value = "main_category")
	private String mainCategory;// 大类,比如:餐饮服务
	private String type;// 中类,比如:中餐
	private String subtype;// 小类
	private String address;// 地址
	@TableField(value = "province_name")
	private String provinceName;// 省
	@TableField(value = "city_name")
	private String cityName;// 市
	@TableField(value = "area_name")
	private String areaName;// 区
	@TableField(value = "lon_wgs84")
	private BigDecimal lonWgs84;
	@TableField(value = "lat_wgs84")
	private BigDecimal latWgs84;
	@TableField(typeHandler = PgGeometryTypeHandler.class)
	private String geom;
	@TableField(exist = false)
	private String geomJson;
	private Integer year;// 年份
	public PoiInfo(String name, String mainCategory, String type, String subtype, String address, String provinceName,
			String cityName, String areaName, BigDecimal lonWgs84, BigDecimal latWgs84, String geom, Integer year) {
		super();
		this.name = name;
		this.mainCategory = mainCategory;
		this.type = type;
		this.subtype = subtype;
		this.address = address;
		this.provinceName = provinceName;
		this.cityName = cityName;
		this.areaName = areaName;
		this.lonWgs84 = lonWgs84;
		this.latWgs84 = latWgs84;
		this.geom = geom;
		this.year = year;
	}
}

        熟悉博主代码风格的小伙伴一定知道,在处理空间数据时,我们需要将Wkt格式的字符数据转为PostGIS认识的Geometry字段,因此在这里就需要自定义typeHandler来进行处理。在上面的代码中标识符如下:

@TableField(typeHandler = PgGeometryTypeHandler.class)

2、属性表数据和空间信息的读取

        在定义好模型实体之后,我们将介绍如何使用GeoTools来进行属性数据和空间信息的读取。通过这两个信息要素,构成完成的一条空间基本信息。在本文的例子中,我们需要指定属性来进行解析,比如需要将“地址”这个属性对应到address中,因此我们需要类似于Jdbc的ResultSet的处理方式,需要手动的进行数据的对应。下面贴出具体的解析代码:

@Test
public void Read2PostGIS() throws IOException, FactoryException {
	// 指定Shapefile的文件路径
	//String shpFile = "C:/BaiduDownload/长沙市2020年POI数据集/长沙市2020年POI数据集/长沙POI数据(.shp)/风景名胜.shp";
	String shpFile = "C:/BaiduDownload/长沙市2020年POI数据集/长沙市2020年POI数据集/长沙POI数据(.shp)/住宿服务.shp";
	//FileDataStore dataStore = FileDataStoreFinder.getDataStore(new File(shpFile));
	ShapefileDataStore shapefileDataStore = new ShapefileDataStore(new File(shpFile).toURI().toURL());
	shapefileDataStore.setCharset(Charset.forName("UTF-8"));// 设置中文字符编码
	// 获取特征类型
	SimpleFeatureType featureType = shapefileDataStore.getSchema(shapefileDataStore.getTypeNames()[0]);
	CoordinateReferenceSystem crs = featureType.getGeometryDescriptor().getCoordinateReferenceSystem();
	System.out.println("坐标参考系统:" + crs);
	Integer epsgCode = CRS.lookupEpsgCode(crs, true);
	SimpleFeatureSource featureSource = shapefileDataStore.getFeatureSource();
	SimpleFeatureCollection simpleFeatureCollection=featureSource.getFeatures();
    SimpleFeatureIterator itertor = simpleFeatureCollection.features();
    //遍历featurecollection
    List<PoiInfo> list = new ArrayList<PoiInfo>();
    Date now = new Date();
    while (itertor.hasNext()){
        SimpleFeature feature = itertor.next();
        Property nameProperty = feature.getProperty("名称");
        String name = (String)nameProperty.getValue();
        Property mainCategoryProperty = feature.getProperty("大类");
        String mainCategory = (String) mainCategoryProperty.getValue();
        Property typeProperty = feature.getProperty("中类");
        String type = (String)typeProperty.getValue();
        Property subtypeProperty = feature.getProperty("小类");
      String subtype = subtypeProperty != null ? (String)subtypeProperty.getValue() : "";
       Property addressProperty = feature.getProperty("地址");
       String address = (String)addressProperty.getValue();
       Property provinceNameProperty = feature.getProperty("省");
       String provinceName = (String)provinceNameProperty.getValue();
       Property cityNameProperty = feature.getProperty("市");
       String cityName = (String)cityNameProperty.getValue();
       Property areaNameProperty = feature.getProperty("区");
       String areaName = (String)areaNameProperty.getValue();
        Property lonWgs84Property = feature.getProperty("WGS84_经");
        BigDecimal lonWgs84 = new BigDecimal(String.valueOf(lonWgs84Property.getValue()));
         Property latWgs84Property = feature.getProperty("WGS84_纬");
         BigDecimal latWgs84 = new BigDecimal(String.valueOf(latWgs84Property.getValue()));
         // 获取空间字段
         org.locationtech.jts.geom.Geometry geometry = (org.locationtech.jts.geom.Geometry) feature.getDefaultGeometry();
         // 创建WKTWriter对象
        WKTWriter wktWriter = new WKTWriter();
        // 将Geometry对象转换为WKT格式的字符串
        String wkt = wktWriter.write(geometry);
        String geom = "SRID=" + epsgCode +";" + wkt;//拼接srid,实现动态写入
        PoiInfo poi = new PoiInfo(name, mainCategory, type, subtype, address, provinceName, cityName, areaName, lonWgs84, latWgs84, geom, 2020);
        poi.setCreateTime(now);
        poi.setUpdateTime(now);
        list.add(poi);
  }
   if(list.size() > 0) {
        // poiService.saveBatch(list,500);
    }
    System.out.println(list.size());
}

        在上面的代码中,请注意在Geometry字段的属性设置时,我们为了能动态的设置空间对象的SRID,需要动态将解析出来的空间参考编码设置到WKT字符串中,方便在数据处理时可以动态的设置。看到很多朋友在介绍相关的博客值,总是在设置方法将4326这个SRID设置为静态的,这样的处理方式不够灵活。

        在第一节中我们曾将讲过,在商务住宅这类POI中,数据的制作方将大类、中类、小类进行了合并,也因此导致了在数据中只有一列,这里举一个合并的例子:

商务住宅;楼宇;商住两用楼宇|商务住宅;楼宇;商务写字楼

        在上面的例子当中,我们就需要特殊处理,正常的大类、中类、小类三类组合起来都是三个长度的标准分类,上面的分类就不是,因此我们将最后的字符全部合并起来,当成当前分类的小类。毕竟这种情况不多,当然我们后续可以对数据进行一个集中的清理。数据转换的逻辑:

String mainCategory = (String) mainCategoryProperty.getValue();;
String [] splitMainCategory = mainCategory.split(";");
String type = "";
String subtype ="";
//商务住宅的POI要特殊处理、从大类中分解出中类和小类
if(splitMainCategory.length == 3) {
     mainCategory = splitMainCategory[0];
     type = splitMainCategory[1];
     subtype = splitMainCategory[2];
}else if(splitMainCategory.length > 3) {
     mainCategory = splitMainCategory[0];
     type = splitMainCategory[1];
     for(int i = 2;i <splitMainCategory.length;i++ ) {
            subtype += splitMainCategory[i];
     }
}else {
     Property typeProperty = feature.getProperty("中类");
     type = (String)typeProperty.getValue();
     Property subtypeProperty = feature.getProperty("小类");
     subtype = subtypeProperty != null ? (String)subtypeProperty.getValue() : "";
}

3、实际运行结果

        最后我们使用Junit来调用上述的代码实现POI数据的批量插入,由于篇幅有限,关于在Mybatis-Plus中如何批量插入数据的代码不再赘述。读取时的数据显示如下:

        可以看到数据已经成功的加载到内存中,等待批量录入的空间数据库中。下面我们来看下空间数据库中的情况。 在PgAdmin中执行查询语句可以看到如下的结果:

        如果能看到以上结果说明,数据已经成功的插入到数据库中,在PgAdmin当中,还可以直接看到空间数据的分布,可以点击查看属性信息。

        以上步骤就是如何在Java中调用GeoTools进行POI数据入库实例。

三、总结

        以上就是本文的主要内容,本文主要讲解在Java开发环境,如何使用Geotools来进行数据的解析与存储,与GDAL的shp数据处理方式不同,在GeoTools中的处理方法有一定的不同。文章分享的方法可以在分布式环境中利用Mybatis-Plus这种ORM框架进行快速的空间数据批量插入。与GeoTools官方提供的PostGIS数据读写相比,本文分享的方法将更加方便,易于与其它项目进行集成。行文仓促,定有不足之处,还恳请各位专家朋友不吝赐教,万分感谢。

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

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

相关文章

大数据技术:Hadoop、Spark与Flink的框架演进

大数据技术&#xff0c;特别是Hadoop、Spark与Flink的框架演进&#xff0c;是过去二十年中信息技术领域最引人注目的发展之一。这些技术不仅改变了数据处理的方式&#xff0c;而且还推动了对数据驱动决策和智能化的需求。在大数据处理领域&#xff0c;选择合适的大数据平台是确…

git 清除二进制文件的 changes 状态

问题&#xff1a;某个分支上修改了二进制文件&#xff0c;导致 changes 一直存在&#xff0c;切换到主分支也仍然存在&#xff0c;点击 Discard 也没用 使用 git reset --hard 还原到初始状态&#xff0c;也不行&#xff0c;不过输出结果会给出错误信息 Encountered 7 file(s) …

raise Exception(“IPAdapter model not found.“)

IPAdapter模型文件太多了&#xff0c;而节点IPAdapter Unified Loader是通过函数&#xff08;get_ipadapter_file与get_clipvision_file&#xff09;预设来加载模型文件&#xff0c;当发生错误“IPAdapter model not found.“时并不指明模型文件名&#xff0c;导致想要有针对性…

C语言 | Leetcode C语言题解之第438题找到字符串中所有字母异位词

题目&#xff1a; 题解&#xff1a; /*** Note: The returned array must be malloced, assume caller calls free().*/ /* *int strCmpn&#xff1a;比较滑动窗口和字符串的相同值 char * s&#xff1a;字符串s&#xff0c;滑动窗口的位置 char * p&#xff1a;字符串p&#…

【Python】Flask-Admin:构建强大、灵活的后台管理界面

在 Web 应用开发中&#xff0c;构建一个直观且功能丰富的后台管理系统对于处理数据和维护应用至关重要。虽然构建一个完全自定义的管理后台界面非常耗时&#xff0c;但 Flask-Admin 提供了一个简洁、灵活的解决方案&#xff0c;可以让开发者快速集成一个功能齐全的后台管理系统…

【移植】轻量系统STM32F407芯片移植案例

往期知识点记录&#xff1a; 鸿蒙&#xff08;HarmonyOS&#xff09;应用层开发&#xff08;北向&#xff09;知识点汇总 鸿蒙&#xff08;OpenHarmony&#xff09;南向开发保姆级知识点汇总~ 持续更新中…… 介绍基于 STM32F407IGT6 芯片在拓维信息 Niobe407 开发板上移植 Op…

Linux操作系统中MongoDB

1、什么是MongoDB 1、非关系型数据库 NoSQL&#xff0c;泛指非关系型的数据库。随着互联网web2.0网站的兴起&#xff0c;传统的关系数据库在处理web2.0网站&#xff0c;特别是超大规模和高并发的SNS类型的web2.0纯动态网站已经显得力不从心&#xff0c;出现了很多难以克服的问…

改变安全策略的五大实践

随着网络威胁形势的加剧&#xff0c;网络安全计划必须不断发展以保护组织的使命。 为了管理这种持续的网络安全发展&#xff0c;应遵循五项关键的安全计划变更管理实践&#xff1a; 1. 识别并吸引受安全风险影响的业务利益相关者 随着新的网络安全风险被发现&#xff0c;受影…

HEITRONICS TC13红外辐射高温计CT13 INFRARED RADIATION PYROMETER CT13

HEITRONICS TC13红外辐射高温计CT13 INFRARED RADIATION PYROMETER CT13

山海优选电商平台卷轴模式订单系统核心架构解析

山海优选卷轴模式的订单核心源码是涉及订单处理、支付、搜索、状态管理等关键功能的代码部分。由于直接提供完整的源代码可能涉及版权和隐私保护问题&#xff0c;我将基于参考文章中的信息&#xff0c;概述该模式订单核心源码的主要结构和功能点。 一、订单核心源码概述 在山海…

IDEA2020运行项目时不从配置的maven仓库找jar包,从C盘默认路径下找jar包

目录 问题描述&#xff1a; 解决方案&#xff1a; 问题描述&#xff1a; 使用IDEA2020做java开发&#xff0c;idea的设置中maven仓库地址配在D盘&#xff0c; maven的配置文件setting.xml中的仓库也已经确认配置到D盘&#xff0c; 项目根据pom文件自动下载jar包时也会下载到…

【Python快速学习笔记01】下载解释器/环境变量配置/PyCharm下载/第一个代码

目录 1.下载python解释器 2.第一个python程序 3.配置解释器环境变量 4.下载开发工具 PyCharm 4.通过PyCharm编写第一个python程序 1.下载python解释器 官网下载&#xff0c;但是下载太慢了&#xff0c;所以直接百度搜了下载了个 Welcome to Python.org 1.官网下载 2.直…

深度伪造语音检测(Deepfake Speech Detection, DSD)全面概述

近期&#xff0c;深度学习技术和神经网络在生成型人工智能领域已取得重大突破。如今&#xff0c;关键的通信媒介&#xff0c;如音频、图像、视频和文本&#xff0c;均能实现自动生成&#xff0c;并广泛应用于诸多领域&#xff0c;包括聊天机器人系统&#xff08;如ChatGPT&…

大数据新视界 --大数据大厂之数据压缩算法比较与应用:节省存储空间

&#x1f496;&#x1f496;&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎你们来到 青云交的博客&#xff01;能与你们在此邂逅&#xff0c;我满心欢喜&#xff0c;深感无比荣幸。在这个瞬息万变的时代&#xff0c;我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…

基于Python实现的国庆节庆祝小程序

祖国母亲即将迎来75周年华诞&#xff0c;在这个特殊的日子里&#xff0c;我们可以用编程的方式来表达对祖国的祝福。本文将使用Python编写一个简单的国庆节庆祝小程序&#xff0c;通过一些编程技巧和设计为国庆节增添一些程序员的特色。 ⭕️庆祝国庆 ⭐️ 程序设计思路&#x…

Netty 与 WebSocket之间的关系

WebSocketProtocolHandler 和 Netty 在处理 WebSocket 连接时扮演不同的角色&#xff0c;但它们通常是一起使用的&#xff0c;尤其是在基于 Netty 的项目中。为了更好地理解它们之间的区别&#xff0c;我们首先需要了解 WebSocket 和 Netty 的基本概念。 WebSocket WebSocket…

超好用的可视化工具!一键生成影响因素森林图,文章增色好帮手!

森林图目前在相关文献中可以说是非常常见了&#xff0c;不只是亚组分析中&#xff0c;普通的回归分析结果也可以用森林图进行可视化展示&#xff0c;不仅可以帮助我们更好地理解不同变量间的一致性和差异性&#xff0c;新颖的图片还能为文章增色不少。 当下主流绘制森林图的方式…

linux蓝屏重启解决方法汇总

前言 linux系统蓝屏&#xff08;Blue Screen Of Death&#xff09;是Linux系统用户遇到最严重的故障&#xff0c;任何新手都无法直接解决它。在遇到蓝屏时&#xff0c;最好的解决方案是联系Linux专业供应商或Linux专业支持工程师&#xff0c;因为他们有系统的协议和经验来解决…

手机如何五开玩梦幻西游端游?用GameViewer远程手机免费畅玩梦幻西游

用手机就能免费玩梦幻西游端游&#xff0c;还可以随时查看挂机进度&#xff01; 想要实现这一点&#xff0c;就用网易GameViewer远程&#xff0c;而且不光手机可以玩梦幻西游端游&#xff0c;平板也能免费玩&#xff0c;并为你实现五开玩梦幻西游端游。 那么&#xff0c;通过Ga…

事后被动处置向事前主动预警转变的智慧工业开源了

智慧工业视觉监控平台是一款功能强大且简单易用的实时算法视频监控系统。它的愿景是最底层打通各大芯片厂商相互间的壁垒&#xff0c;省去繁琐重复的适配流程&#xff0c;实现芯片、算法、应用的全流程组合&#xff0c;从而大大减少企业级应用约95%的开发成本。用户只需在界面上…