# 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));
}
```