# lgis **Repository Path**: wensten/lgis ## Basic Information - **Project Name**: lgis - **Description**: WebGIS 系统Spring Boot后端开发常见功能封装 - **Primary Language**: Java - **License**: GPL-3.0 - **Default Branch**: master - **Homepage**: https://lgis.gitee.io/ - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 2 - **Created**: 2025-05-11 - **Last Updated**: 2025-05-11 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 简介 本项目封装了 WebGIS 项目中常见的后端操作。 其中,`lgis-common`封装了常规 Spring Boot 项目中常见的后端操作,例如,提供了针对 PostgreSQL 特性字段类型的处理类、封装了通用 Redis 工具类; `lgis-tools`封装了常见的 GIS 操作,例如:提供了 Shapefile 与 GeoJSON 的互转功能。 # lgis-common `lgis-common` 基于 Spring Boot 2.x 版本实现,主要功能如下: - 封装对 MyBatis Plus 的常用操作,包括:分页与乐观锁的配置、基础实体类、常用类型处理器等 - 封装常见的请求参数结果,包括字典参数、分页查询参数、分页查询请求响应、目录树结构等 - 封装统一响应拦截、全局异常拦截 - 封装 Redis 配置以及通用的 Redis 操作工具类 - 封装常见的文件操作,提供一些 Excel 文件操作功能 - 封装其他适用的工具类,包括 Bean 拷贝、日期/时间处理工具类、URL 处理工具类 ## 配置 在`pom.xml`文件中引入本项目,另外还需要引入 spring-boot 相关依赖。 ``` org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-starter-actuator org.springframework.boot spring-boot-starter-validation cn.funnymap lgis-common 0.4.10 ``` 如果需要用到 MyBatis Plus,还需要显示引入该依赖。 > 以 PostgreSQL 数据库为例 ```text org.postgresql postgresql net.postgis postgis-jdbc 2021.1.0 com.baomidou mybatis-plus-boot-starter 3.5.6 ``` ## 开始使用 ### 关系型数据库相关操作 #### 数据表基础字段 本项目结合多个实际项目中的读取,提取数据表一些通用字段,具体字段信息如下。 > 具备布尔类型时,例如:PostgreSQL 数据库 | 字段名称 | 字段类型 | 字段描述 | 是否为空 | 默认值 | |:------------:|:---------:|:------:|:----:|:-----:| | isVisible | boolean | 是否可见 | 否 | true | | isDeleted | boolean | 逻辑删除字段 | 否 | false | | creator_id | varchar | 创建人ID | 是 | | | creator_name | varchar | 创建人姓名 | 是 | | | create_time | timestamp | 创建时间 | 是 | | | updater_id | varchar | 更新人ID | 是 | | | updater_name | varchar | 更新人姓名 | 是 | | | update_time | timestamp | 更新时间 | 是 | | > 不具备布尔类型时,例如:MySQL 数据库 | 字段名称 | 字段类型 | 字段描述 | 是否为空 | 默认值 | |:------------:|:---------:|:-------------------:|:----:|:---:| | isVisible | int | 是否可见,1表示可见,0表示不可见 | 否 | 1 | | isDeleted | int | 逻辑删除字段,1表示删除,0表示未删除 | 否 | 0 | | creator_id | varchar | 创建人ID | 是 | | | creator_name | varchar | 创建人姓名 | 是 | | | create_time | timestamp | 创建时间 | 是 | | | updater_id | varchar | 更新人ID | 是 | | | updater_name | varchar | 更新人姓名 | 是 | | | update_time | timestamp | 更新时间 | 是 | | #### 数据库实体定义 如果数据表具备上述基础字段,在定义数据表对应的实体类时,可继承`BaseEntity`或者`BaseEntityLogicByInteger`,前者使用布尔类型实现逻辑删除字段的映射,后者使用整型实现逻辑删除字段的映射。 > 示例代码 ```java @EqualsAndHashCode(callSuper = true) @Data @TableName(value = "t_user") class TUserEntity extends BaseEntity { @TableId(type = IdType.ASSIGN_ID) private String id; private String username; private String password; } ``` #### 数据库字段填充 `lgis-common`提供了对`creatorId`、`creatorName`、`createTime`、`updaterId`、`updaterName`、`updateTime `字段的默认填充功能。其中,对于`updateTime`、`createTime`字段,默认填充语句执行时的时间,对于新增语句,这两个字段的值是一样的;对于其它4个字段,优先通过`UserBasicInfoContext `中读取用户信息,然后填充到对应的字段。 `UserBasicInfoContext`是一个静态类,提供了保存用户信息的功能,是线程安全的。在实际项目中,通过权限框架解析到用户信息后,可将基本的用户信息提供给`UserBasicInfoContext`。 > 通过 UserBasicInfoContext 传递用户信息的示例 ```java // 定义需要传递的用户基本信息 UserBasicInfo userInfo = new UserBasicInfo("用户ID", "用户名", List.of("用户角色")); // 保存用户基本信息 UserBasicInfoContext.setCurrentUserInfo(userInfo); ``` #### 特殊字段类型转换 `lgis-common` 基于 MyBatis Plus 封装了对字符串数组、JSON、JSON 数组、LTREE 以及几何类型的类型处理能力。 > 字符串数组 ```java @EqualsAndHashCode(callSuper = true) @Data @TableName(value = "t_user", autoResultMap = true) class TUserEntity extends BaseEntity { @TableId(type = IdType.ASSIGN_ID) private String id; private String username; private String password; @TableField(typeHandler = StringArrayTypeHandler.class) private List roles; } ``` > JSON、JSON 数组,需要在 pom.xml 文件中引入 fastjson2 依赖 ```java @EqualsAndHashCode(callSuper = true) @Data @TableName(value = "t_server", autoResultMap = true) class TServerEntity extends BaseEntity { @TableId(type = IdType.ASSIGN_ID) private String id; private String hostname; private String ip; @TableField(typeHandler = JsonbWithJsonArrayTypeHandler.class) private JSONArray softwares; @TableField(typeHandler = JsonbTypeHandler.class) private JSONObject metadata; } ``` > LTREE,PostgreSQL 字段类型,需要在对应的数据库中创建 ltree 扩展 ```java @EqualsAndHashCode(callSuper = true) @Data @TableName(value = "t_tree", autoResultMap = true) class TTreeEntity extends BaseEntity { @TableId(type = IdType.ASSIGN_ID) private String id; private String name; @TableField(typeHandler = LTreeTypeHandler.class) private String path; private String cnPath; } ``` > 几何类型,需要在 pom.xml 中引入依赖 ```java @EqualsAndHashCode(callSuper = true) @Data @TableName(value = "t_image", autoResultMap = true) class TImageEntity extends BaseEntity { @TableId(type = IdType.ASSIGN_ID) private String id; private String name; @TableField(typeHandler = GeometryTypeHandler.class) private String extent; } ``` #### 分页查询 `lgis-common` 封装了分页查询相关的请求参数和响应结构,分别为`CommonPageQueryRequest`和`CommonPageQueryResponse`。 > CommonPageQueryRequest.java ```java public class CommonPageQueryRequest { @NotNull(message = "分页页码不可为空") private Integer page; @NotNull(message = "分页大小不可为空") private Integer pageSize; private String name; } ``` > CommonPageQueryResponse.java ```java public class CommonPageQueryResponse { private Long total; private List items; } ``` > 分页查询示例:UserInfoPageQueryRequestDTO,定义分页查询参数 ```java @EqualsAndHashCode(callSuper = true) @Data class UserInfoPageQueryRequestDTO extends CommonPageQueryReqeust { private String departmentId; } ``` > 分页查询:UserInfoPageQueryResponseItem,定义分页查询响应结果中数据项的结构 ```java @Data class UserInfoPageQueryResponseItem { private String id; private String name; private String departmentName; } ``` > 分页请求参数使用示例:Controller,注意`@Validated`注解,使得对 page、pageSize 的校验规则生效 ```java @Validated @Setter(onMethod_ = {@Autowired}) @RestController @RequestMapping("/users") public class UserController { private UserService userService; @GetMapping public CommonPageQueryResponse queryByPage( UserInfoPageQueryRequestDTO pageQueryRequestDTO) { return userService.queryPage(pageQueryRequestDTO); } } ``` > 分页请求参数使用示例:Service, ```java @Service public class UserService extends ServiceImpl { public CommonPageQueryResponse queryByPage( UserInfoPageQueryRequestDTO pageQueryRequestDTO) { IPage page = this.executePageQuery(pageQueryRequestDTO); List pageQueryResponseItemList = page.getRecords().stream().map (this::entity2PageQueryResponseItem).collect(Collectors.toList()); return new CommonPageQueryResponse<>(page.getTotal(), pageQueryResponseItemList); } private IPage executePageQuery(UserInfoPageQueryRequestDTO pageQueryRequestDTO) { IPage page = TUserEntity.getPage(pageQueryRequestDTO); LambdaQueryWrapper queryWrapper = this.buildPageQueryWrapper(pageQueryRequestDTO); return this.page(page, queryWrapper); } private LambdaQueryWrapper buildPageQueryWrapper(UserInfoPageQueryRequestDTO pageQueryRequestDTO) { LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper(); // 省略构建查询语句的代码 return queryWrapper; } private UserInfoPageQueryResponseItem entity2PageQueryResponseItem(TUserInfoEntity entity) { UserInfoPageQueryResponseItem item = new UserInfoPageQueryResponseItem(); // 省略从数据表实体转为分页查询结构的代码 return item; } } ``` ### 统一响应拦截 在`pom.xml`文件中引用`lgis-common`后,无需配置即可集成统一响应拦截功能。统一响应结构如下: ```json { "code": "业务状态码,Integer", "status": "响应状态,String,success或者fail", "message": "响应消息,String", "data": "响应数据,Object" } ``` 其中,对于`POST`、`PUT`请求,HTTP 状态码为 201,对于`GET`、`DELETE`请求,HTTP 状态码为 200。 统一响应类`ResponseStructure`提供了如下静态方法: - `success(Integer code, String message, T data)` - `success(String message)` - `success(T data)` - `success()` - `created()` - `created(String message)` - `deleted()` - `deleted(String message)` - `fail(Integer code, String message)` - `fail(AbstractException exception)` - `instance(Integer code, StatusEnum status, String message, T data)` ### 全局异常拦截 在`pom.xml`文件中引用`lgis-common`后,无需配置即可集成全局异常拦截功能。 ### Redis 配置及通用工具 使用 Redis 工具需要在`pom.xml`添加如下引用。 ``` org.springframework.boot spring-boot-starter-data-redis provided ``` `lgis-common` 默认集成了 Redis 配置类,使用`StringRedisSerializer`对键做序列化,使用自定义的`CustomGenericJackson2JsonRedisSerializer `对值做序列化,如果值的类型是字符串,则按原格式保存,对于其他类型,则使用 JSON 格式保存。 `lgis-common` 也提供了 Redis 工具类`RedisUtils`,封装了常用的操作。 ### 请求参数结构封装 `lgis-common` 除了提供上述分页查询、分页响应的基础参数结构以外,还提供了通用的字典结构`CommonDict`、有子节点的字典结构`CommonDictWithChildren`、日期范围`DateRange`等。 > CommonDict.java ```java @Data @NoArgsConstructor @AllArgsConstructor public class CommonDict { private String label; private String value; } ``` > CommonDictWithChildren.java ```java @EqualsAndHashCode(callSuper = true) @AllArgsConstructor @NoArgsConstructor @Data public class CommonDictWithChildren extends CommonDict { private List children = new ArrayList<>(); } ``` > DateRange.java ```java @Data @DateRangeValidation public class DateRange { private String start; private String end; } ``` 另外,`lgis-common`也封装了一些常见的参数校验工具,包括: - 日志范围校验:`DateRangeValidation` - MultipartFile 格式校验:`MultipartFileExtensionAnno` ### 其他工具 #### 日期工具类 `lgis-common` 提供了日期工具类`DateUtil`,提供日期、时间转换工具。 - `str2Date(String dateAsStr)`:字符串格式转 Date,默认格式`yyyy-MM-dd` - `str2Date(String dateAsStr, String format)`:按照指定的格式将字符串转为 Date - `date2str(Date date)`:按照默认格式将 Date 转为字符串格式 - `date2str(Date date, String format)`:按指定的格式将 Date 转为字符串格式 - `str2LocalDateTime(String dateTimeAsStr)`:按照默认格式将字符串格式转为 LocalDateTime,默认格式`yyyy-MM-dd HH:mm:ss` - `str2LocalDateTime(String dateTimeAsStr, String format)`:按照指定的格式将字符串格式转为 LocalDateTime - `localDateTime2Str(LocalDateTime localDateTime)`:按照默认格式将 LocalDateTime 转为字符串 - `localDateTime2Str(LocalDateTime localDateTime, String format)`:按照指定格式将 LocalDateTime 转为字符串 - `dateRangeDto2DateList(String from, String to)`:将字符串格式的时间区间转为 Data 列表 - `plus(Integer value)`:在当前日期上做加法,如果是正整数,则返回几天后的日期,如果是负数,则返回前面几天的日期 - `localDateTime2Date(LocalDateTime localDateTime, ZoneId zoneId)`:按照指定的时区,将 LocalDateTime 转为 Date - `localDateTime2Date(LocalDateTime localDateTime)`:按照系统默认时间将 LocalDateTime 转为 Date #### UUID 工具类 `lgis-common` 提供了`UuidUtil`工具类,封装了 UUID 相关操作。 - `random()`:生成随机的 UUID 字符串 - `random(String value)`:基于指定的值生成随机的 UUID 字符串 - `randomWithoutStrike()`:生成随机的不带`-`的 UUID 字符串 - `randomWithoutStrike(String value)`:基于指定的值生成随机的不带`-`的 UUID 字符串 - `replaceStrikeByTargetChar(String replaceStr)`:生成随机的并使用指定字符替换`-`的 UUID 字符串 - `replaceStrikeByTargetChar(String replaceStr, String value)`:基于指定的值生成随机的并使用指定字符替换`-`的 UUID 字符串 - `fromString(String value)`:字符串格式转 UUID #### URL 工具类 `lgis-common` 提供了`UrlUtil`工具类,封装了一些对 URL 的操作。 - `URL splitQueryParameters(URL url)`:去掉 URL 中的查询参数 #### File 工具类 `lgis-common` 提供了`FileUtil`工具类,封装了一些常见的文件操作。 - `getDirectorySize(Path directory)`:获取指定文件夹的大小 - `readableFileSize(long size)`:格式文件大小,将指定的字节大小转为可读性更好的字符串 - `getFileByFileExtension(Path targetDir, String... extensions)`:在指定文件夹中(包含其子级文件夹)获取指定文件扩展的文件路径 - `getFileByFileName(Path targetDir, String fileName, boolean isNested)`:在指定文件夹中获取指定名称的文件路径 - `getFileExtension(Path path)`:获取指定文件的文件扩展名 - `getFileExtension(String fileName)`:从文件名称中获取文件扩展名 - `getFileNameWithoutExtension(Path path)`:获取不带有文件扩展的文件名 - `extractZipFileToTemp(Path zipFilePath)`:将ZIP文件解压到临时目录 - `extractZipFile(Path zipFilePaht, Path targetDir)`:将 ZIP 文件解压到指定目录 - `saveMultipartFileToTemp(MultipartFile multipartFile)`:将 MultipartFile 保存到临时目录 - `validateFileType(String fileName, FileType targetFileType)`:校验文件类型 - `validateFileType(Path path, FileType targetFileType)`:校验文件类型 - `delete(Path path)`:删除文件夹/文件 - `getFileByFuzzyMatchingFilename(Path targetDir, String fileName, boolean isNested)`:在指定文件夹中获取指定名称的文件路径 # lgis-tools `lgis-tools` 核心依赖于 GeoTools 实现,主要功能如下: - 封装对 Shapefile 的多种操作 - 读取 Shapefile 文件属性 - Shapefile 转 WKT 字符串、Shapefile 转 GeoJSON - 基于 WKT 字符串生成 Shapefile 文件 - 基于 ESRI Graphic 生成 Shapefile 文件 - 封装对 WKT 字符串常用操作,例如:多个 WKT 字符串合并成一个、提取 WKT 的几何类型等 - 封装对坐标参考的常见操作 ## 配置 在`pom.xml`文件中引入本项目,另外还需要引入 spring-boot 相关依赖。 在`pom.xml`文件中引入本项目,另外还需要引入 spring-boot 相关依赖。 ``` org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-starter-actuator org.springframework.boot spring-boot-starter-validation cn.funnymap lgis-tools 0.6.1 ``` ## 开始使用 ### 矢量文件操作 #### Shapefile 压缩包转 GeoJSON ```java public void shapefileZip2Geojson() { ShpParseParameter shpParseParameter = new ShpParseParameter(basePath, GeographicCRS.CGCS2000); ShpParser> shpParser = new Shp2GeojsonParser(shpParseParameter); List geojsonResponseList = shpParser.execute(); System.out.println(geojsonResponseList); } ``` #### Shapefile 压缩表转 WKT ```java public void shapefileZip2Wkt() { ShpParseParameter shpParseParameter = new ShpParseParameter(basePath, GeographicCRS.CGCS2000); ShpParser> shpParser = new Shp2WktParser(shpParseParameter); List wktList = shpParser.execute(); System.out.println(wktList); } ``` #### 读取 Shapefile 属性值 ```java public void shapefileAttributeValueParser() { ShpParseParameter shpParseParameter = new ShpParseParameter(basePath, GeographicCRS.CGCS2000); ShpParser> shpParser = new ShpAttributeValueParser(shpParseParameter); List attributeValueList = shpParser.execute(); System.out.println(attributeValueList.get(0).getValues().get(0)); } ``` #### WKT 转 Shapefile ```java public void wkt2PolygonTest() { Path path = basePath.resolve("polygon_example.shp"); String wkt = "POLYGON((93 31, 93 32, 94 32, 94 31, 93 31))"; HashMap values = new LinkedHashMap<>(); values.put("name", "图形1"); values.put("year", 2024); List fieldList = new ArrayList<>(); fieldList.add(new ShpField("name", "name", String.class)); fieldList.add(new ShpField("year", "year", Integer.class)); Wkt2ShpGeneratorParam converterParam = new Wkt2ShpGeneratorParam( GeographicCRS.CGCS2000, fieldList, Collections.singletonList(wkt), Collections.singletonList(values)); ShpGenerator shpGenerator = new WKT2PolygonGenerator(path, converterParam); shpGenerator.run(); assertTrue(Files.exists(path)); } ``` #### GeoJSON 转 Shapefile ```java public void geojsonPoint2ShapefileTest() { Path path = basePath.resolve("geojson_point_example.shp"); String polygonGeojson = "[{\"geometry\":{\"spatialReference\":{\"wkid\":4490},\"x\":94.66798245906827,\"y\":43.13475966453552,\"type\":\"point\"},\"symbol\":{\"type\":\"simple-marker\",\"color\":[255,255,255,1],\"angle\":0,\"xoffset\":0,\"yoffset\":0,\"size\":6,\"style\":\"esriSMSCircle\",\"outline\":{\"type\":\"simple-line\",\"color\":[50,50,50,1],\"width\":1,\"style\":\"esriSLSSolid\"}},\"attributes\":{\"类型\":\"b\",\"时间\":\"2024-06-11\",\"说明\":\"水水水水\",\"金额\":0,\"编号\":0}},{\"geometry\":{\"spatialReference\":{\"wkid\":4490},\"x\":70.44922292232513,\"y\":42.15819954872131,\"type\":\"point\"},\"symbol\":{\"type\":\"simple-marker\",\"color\":[255,255,255,1],\"angle\":0,\"xoffset\":0,\"yoffset\":0,\"size\":6,\"style\":\"esriSMSCircle\",\"outline\":{\"type\":\"simple-line\",\"color\":[50,50,50,1],\"width\":1,\"style\":\"esriSLSSolid\"}},\"attributes\":{\"类型\":\"a\",\"时间\":\"2024-06-11\",\"说明\":\"哈哈哈哈哈哈哈\",\"金额\":0,\"编号\":0}}]"; List fieldList = new ArrayList<>(); fieldList.add(new ShpField("类型", "类型", String.class)); fieldList.add(new ShpField("时间", "时间", Date.class)); fieldList.add(new ShpField("说明", "说明", String.class)); fieldList.add(new ShpField("金额", "金额", Integer.class)); fieldList.add(new ShpField("编号", "编号", Integer.class)); EsriGraphic2ShpGeneratorParam generatorParam = new EsriGraphic2ShpGeneratorParam(GeographicCRS.CGCS2000, JSONArray.parse(polygonGeojson), fieldList); ShpGenerator shpGenerator = new EsriGraphic2ShpGenerator(path, generatorParam); shpGenerator.run(); assertTrue(Files.exists(path)); } ```