(依旧叠甲,我也是菜鸟,悉知我的功力还很浅薄,如果有哪里做的不行欢迎批评指正,大部分代码都是借助AI辅助编写的,可能会有些问题,遇到可留言指正)
Day 3:
书接上回,咱是用写了一下基本代码(其实还有很多细节,但是不是很重要,大家可以看看老师的教程,自己ai补补),接下来咱们开始写一下这些代码。
"以上所有代码均已在本地跑通,JDK 21 + Spring Boot 3.5.14 + PostgreSQL 17,可以放心使用。"
一、搭建基础 CRUD 结构
1.model类
首先是PointModel,先加两个参数,与数据库对应上,先把流程跑通,springboot要善用注解,@Getter和@Setter,就不用写getter和setter了。
路径:src/main/java/com/history/gismap/model/PointModel.java
package com.history.gismap.model; import lombok.Getter; import lombok.Setter; import lombok.ToString; import org.locationtech.jts.geom.Geometry; @Getter @Setter @ToString public class PointModel { private Integer gId; private String nameCh; private Geometry geometry; }说明:使用 Lombok 注解简化 getter/setter/toString,geometry 字段使用 JTS 的 Geometry 类型。
2.Dao类
注意把class改成了interface,加了@Service注释,只新建了一个getCntyPoint的方法,跑下流程,加@Param注释,注释里的gId是sql中的#{gId}。
路径:src/main/java/com/history/gismap/dao/MapDao.java
package com.history.gismap.dao; import com.history.gismap.model.GeometryModel; import com.history.gismap.model.PointModel; import org.apache.ibatis.annotations.Param; import java.util.List; public interface MapDao { PointModel getCntyPoint(@Param("gId") Integer gId); int insertCntyPoint(PointModel pointModel); int updateCntyPoint(PointModel pointModel); int deleteCntyPoint(@Param("gId") Integer gId); List<GeometryModel> getDynastyGeom(@Param("category") String category); List<GeometryModel> getAllGeometry(); }3. 创建 Mapper XML 映射文件
路径:src/main/resources/mapper/HistoryGISMapper.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.history.gismap.dao.MapDao"> <resultMap id="pointModelMap" type="com.history.gismap.model.PointModel"> <id property="gId" column="gid"/> <result property="nameCh" column="name_ch"/> <result property="geometry" column="geom" typeHandler="com.history.gismap.mybatis.GeometryTypeHandler"/> </resultMap> <resultMap id="geometryModelMap" type="com.history.gismap.model.GeometryModel"> <id property="gId" column="gid"/> <result property="namePy" column="name_py"/> <result property="nameCh" column="name_ch"/> <result property="nameFt" column="name_ft"/> <result property="presLoc" column="pres_loc"/> <result property="typePy" column="type_py"/> <result property="typeCh" column="type_ch"/> <result property="levRank" column="lev_rank"/> <result property="begYr" column="beg_yr"/> <result property="begRule" column="beg_rule"/> <result property="endYr" column="end_yr"/> <result property="endRule" column="end_rule"/> <result property="geoSrc" column="geo_src"/> <result property="compiler" column="compiler"/> <result property="gecomplr" column="gecomplr"/> <result property="checker" column="checker"/> <result property="entDate" column="ent_date"/> <result property="begChgTy" column="beg_chg_ty"/> <result property="endChgTy" column="end_chg_ty"/> <result property="geometry" column="geom" typeHandler="com.history.gismap.mybatis.GeometryTypeHandler"/> </resultMap> <select id="getCntyPoint" resultMap="pointModelMap"> SELECT gid, name_ch, geom FROM v6_time_cnty_pts_utf_wgs84 WHERE gid = #{gId} </select> <select id="getAllGeometry" resultMap="geometryModelMap"> SELECT gid, name_py, name_ch, name_ft, pres_loc, type_py, type_ch, lev_rank, beg_yr, beg_rule, end_yr, end_rule, geo_src, compiler, gecomplr, checker, ent_date, beg_chg_ty, end_chg_ty, geom FROM v6_time_cnty_pts_utf_wgs84 </select> <select id="getDynastyGeom" resultMap="geometryModelMap"> SELECT gid, name_py, name_ch, name_ft, pres_loc, type_py, type_ch, lev_rank, beg_yr, beg_rule, end_yr, end_rule, geo_src, compiler, gecomplr, checker, ent_date, beg_chg_ty, end_chg_ty, geom FROM v6_time_cnty_pts_utf_wgs84 WHERE name_ch = #{category} </select> <insert id="insertCntyPoint" parameterType="com.history.gismap.model.PointModel"> INSERT INTO v6_time_cnty_pts_utf_wgs84 (name_ch, geom) VALUES (#{nameCh}, #{geometry, typeHandler=com.history.gismap.mybatis.GeometryTypeHandler}) </insert> <update id="updateCntyPoint" parameterType="com.history.gismap.model.PointModel"> UPDATE v6_time_cnty_pts_utf_wgs84 SET name_ch = #{nameCh}, geom = #{geometry, typeHandler=com.history.gismap.mybatis.GeometryTypeHandler} WHERE gid = #{gId} </update> <delete id="deleteCntyPoint"> DELETE FROM v6_time_cnty_pts_utf_wgs84 WHERE gid = #{gId} </delete> </mapper>关键点:
namespace必须等于MapDao的全限定名每个
resultMap中 geometry 字段要关联GeometryTypeHandlerINSERT/UPDATE 中 geometry 字段也要指定
typeHandler属性数据库表名
v6_time_cnty_pts_utf_wgs84根据实际情况替换
4. 创建 Service 接口 MapService
路径:src/main/java/com/history/gismap/service/MapService.java
package com.history.gismap.service; import com.history.gismap.model.GeometryModel; import com.history.gismap.model.PointModel; import java.util.List; public interface MapService { PointModel getCntyPoint(Integer gId); int insertCntyPoint(PointModel pointModel); int updateCntyPoint(PointModel pointModel); int deleteCntyPoint(Integer gId); List<GeometryModel> getDynastyGeom(String category); List<GeometryModel> getAllGeometry(); }5 创建 Service 实现 MapServiceImpl
路径:src/main/java/com/history/gismap/service/impl/MapServiceImpl.java
package com.history.gismap.service.impl; import com.history.gismap.dao.MapDao; import com.history.gismap.model.GeometryModel; import com.history.gismap.model.PointModel; import com.history.gismap.service.MapService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; @Service public class MapServiceImpl implements MapService { @Autowired private MapDao mapDao; @Override public PointModel getCntyPoint(Integer gId) { return mapDao.getCntyPoint(gId); } @Override public int insertCntyPoint(PointModel pointModel) { return mapDao.insertCntyPoint(pointModel); } @Override public int updateCntyPoint(PointModel pointModel) { return mapDao.updateCntyPoint(pointModel); } @Override public int deleteCntyPoint(Integer gId) { return mapDao.deleteCntyPoint(gId); } @Override public List<GeometryModel> getDynastyGeom(String category) { return mapDao.getDynastyGeom(category); } @Override public List<GeometryModel> getAllGeometry() { return mapDao.getAllGeometry(); } }说明:@Service注册为 Spring Bean,@Autowired注入 MapDao(MyBatis 通过@MapperScan自动生成代理对象)。
6. 创建 Controller MapController
路径:src/main/java/com/history/gismap/controller/MapController.java
package com.history.gismap.controller; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.history.gismap.model.GeometryModel; import com.history.gismap.model.PointModel; import com.history.gismap.service.MapService; import com.history.gismap.service.impl.GeometryCache; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.extern.slf4j.Slf4j; import org.geotools.geojson.geom.GeometryJSON; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; import java.io.StringWriter; import java.util.List; @Slf4j @Tag(name = "历史GIS接口") @Controller @RequestMapping("/history") public class MapController { @Autowired private MapService mapService; @Autowired private GeometryCache geometryCache; @Operation(summary = "根据ID查询点位") @GetMapping("/pointmodel") @ResponseBody public JSONObject getPoint(@Parameter(description = "点位ID") @RequestParam("gid") Integer gid) { JSONObject result = new JSONObject(); try { PointModel point = mapService.getCntyPoint(gid); if (point != null) { JSONObject data = new JSONObject(); data.put("gId", point.getGId()); data.put("nameCh", point.getNameCh()); if (point.getGeometry() != null) { GeometryJSON geometryJSON = new GeometryJSON(); StringWriter writer = new StringWriter(); geometryJSON.write(point.getGeometry(), writer); data.put("geometry", JSONObject.parseObject(writer.toString())); } result.put("code", 200); result.put("data", data); } else { result.put("code", 404); result.put("message", "Point not found for gid=" + gid); } } catch (Exception e) { log.error("getPoint failed, gid={}", gid, e); result.put("code", 500); result.put("message", e.getMessage()); } return result; } @Operation(summary = "根据地名查询GeoJSON") @GetMapping("/geometry") @ResponseBody @CrossOrigin public JSONObject getGeometry(@Parameter(description = "地名") @RequestParam String category) { JSONObject result = new JSONObject(); try { List<GeometryModel> list = geometryCache.getByCategory(category); if (list == null) { log.warn("Cache miss for category={}, falling back to database", category); list = mapService.getDynastyGeom(category); } JSONArray jsonArray = new JSONArray(); GeometryJSON geometryJSON = new GeometryJSON(); for (GeometryModel model : list) { JSONObject item = new JSONObject(); item.put("gId", model.getGId()); item.put("namePy", model.getNamePy()); item.put("nameCh", model.getNameCh()); item.put("nameFt", model.getNameFt()); item.put("presLoc", model.getPresLoc()); item.put("typePy", model.getTypePy()); item.put("typeCh", model.getTypeCh()); item.put("levRank", model.getLevRank()); item.put("begYr", model.getBegYr()); item.put("begRule", model.getBegRule()); item.put("endYr", model.getEndYr()); item.put("endRule", model.getEndRule()); item.put("geoSrc", model.getGeoSrc()); item.put("compiler", model.getCompiler()); item.put("gecomplr", model.getGecomplr()); item.put("checker", model.getChecker()); item.put("entDate", model.getEntDate()); item.put("begChgTy", model.getBegChgTy()); item.put("endChgTy", model.getEndChgTy()); if (model.getGeometry() != null) { StringWriter writer = new StringWriter(); geometryJSON.write(model.getGeometry(), writer); item.put("geometry", JSONObject.parseObject(writer.toString())); } jsonArray.add(item); } result.put("number", list.size()); result.put("list", jsonArray); log.info("getGeometry success, category={}, count={}", category, list.size()); } catch (Exception e) { log.error("getGeometry failed, category={}", category, e); result.put("number", 0); result.put("list", new JSONArray()); result.put("error", e.getMessage()); } return result; } @Operation(summary = "新增点位") @PostMapping("/add") @ResponseBody public int addPoint(PointModel pointModel) { return mapService.insertCntyPoint(pointModel); } @Operation(summary = "修改点位") @PostMapping("/modify") @ResponseBody public int modifyPoint(PointModel pointModel) { return mapService.updateCntyPoint(pointModel); } @Operation(summary = "删除点位") @GetMapping("/remove") @ResponseBody public int removePoint(@Parameter(description = "点位ID") @RequestParam("gid") Integer gid) { return mapService.deleteCntyPoint(gid); } }注意 1:getPoint方法中不能直接把point对象传给 FastJSON,因为 JTS 的Geometry类有getEnvelope()方法导致无限递归。必须手动构造 JSONObject,用GeometryJSON把 geometry 转为标准 GeoJSON 格式。注意 2:使用@Tag、@Operation、@Parameter注解可为 Knife4j/Swagger 页面提供中文描述,方便前端查看和调试。
7. 修改启动类 GismapApplication
路径:src/main/java/com/history/gismap/GismapApplication.java
package com.history.gismap; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication @MapperScan("com.history.gismap.dao") public class GismapApplication { public static void main(String[] args) { SpringApplication.run(GismapApplication.class, args); } }8 .配置文件
路径:src/main/resources/application.yml
server: port: 8080 spring: datasource: driver-class-name: org.postgresql.Driver url: jdbc:postgresql://127.0.0.1:5432/postgres username: postgres password: "0164523" druid: initial-size: 8 min-idle: 5 max-active: 10 query-timeout: 6000 transaction-query-timeout: 6000 remove-abandoned-timeout: 1800 filters: stat,config mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.history.gismap.model logging: config: classpath:logback-boot.xml注意 1:password必须加引号,否则以 0 开头的数字会被 YAML 解析为八进制。注意 2:不需要手动指定spring.datasource.type,druid-spring-boot-3-starter会自动配置。
路径:src/main/resources/logback-boot.xml
<?xml version="1.0" encoding="UTF-8"?> <configuration> <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern> </encoder> </appender> <root level="INFO"> <appender-ref ref="CONSOLE"/> </root> </configuration>二、编写 TypeHandler 处理 PostgreSQL geometry 字段
1. 创建 GeometryTypeHandle
路径:src/main/java/com/history/gismap/mybatis/GeometryTypeHandler.java
package com.history.gismap.mybatis; import org.apache.ibatis.type.BaseTypeHandler; import org.apache.ibatis.type.JdbcType; import org.apache.ibatis.type.MappedTypes; import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.io.WKBReader; import org.postgresql.util.PGobject; import java.sql.CallableStatement; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; @MappedTypes(Geometry.class) public class GeometryTypeHandler extends BaseTypeHandler<Geometry> { @Override public void setNonNullParameter(PreparedStatement ps, int i, Geometry parameter, JdbcType jdbcType) throws SQLException { PGobject pgObject = new PGobject(); pgObject.setType("geometry"); pgObject.setValue(parameter.toText()); ps.setObject(i, pgObject); } @Override public Geometry getNullableResult(ResultSet rs, String columnName) throws SQLException { PGobject pgObject = (PGobject) rs.getObject(columnName); if (pgObject == null) return null; return convertToGeometry(pgObject.getValue()); } @Override public Geometry getNullableResult(ResultSet rs, int columnIndex) throws SQLException { PGobject pgObject = (PGobject) rs.getObject(columnIndex); if (pgObject == null) return null; return convertToGeometry(pgObject.getValue()); } @Override public Geometry getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { PGobject pgObject = (PGobject) cs.getObject(columnIndex); if (pgObject == null) return null; return convertToGeometry(pgObject.getValue()); } private Geometry convertToGeometry(String hexWkb) throws SQLException { if (hexWkb == null || hexWkb.isEmpty()) return null; try { byte[] wkbBytes = WKBReader.hexToBytes(hexWkb); return new WKBReader().read(wkbBytes); } catch (Exception e) { throw new SQLException("Failed to parse WKB geometry: " + e.getMessage(), e); } } }核心转换逻辑
| 方向 | 方法 | 说明 |
|---|---|---|
| Java → 数据库 | setNonNullParameter | 用PGobject包装 Geometry 的 WKT 字符串,type设为"geometry" |
| 数据库 → Java | convertToGeometry | 从PGobject取出 hex WKB 字符串,用WKBReader.hexToBytes转 byte 数组,再WKBReader.read还原为 Geometry |
在 Mapper XML 中使用
在resultMap中关联 TypeHandler:
<result property="geometry" column="geom" typeHandler="com.history.gismap.mybatis.GeometryTypeHandler"/>三、将数据库查询结果转为 GeoJSON 格式返回
1. 添加 GeoTools 依赖
在pom.xml中添加:
<properties> <geotools.version>31.0</geotools.version> </properties> <dependencies> <dependency> <groupId>org.geotools</groupId> <artifactId>gt-geojson</artifactId> <version>${geotools.version}</version> </dependency> </dependencies> <repositories> <repository> <id>osgeo</id> <name>OSGeo Release Repository</name> <url>https://repo.osgeo.org/repository/release/</url> <snapshots><enabled>false</enabled></snapshots> <releases><enabled>true</enabled></releases> </repository> </repositories>完整 的pom.xml如下:
路径:pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.5.14</version> <relativePath/> </parent> <groupId>com.history</groupId> <artifactId>gismap</artifactId> <version>0.0.1-SNAPSHOT</version> <name>gismap</name> <properties> <java.version>21</java.version> <geotools.version>31.0</geotools.version> </properties> <dependencies> <!-- Spring Boot --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- MyBatis --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>3.0.5</version> </dependency> <!-- PostgreSQL --> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> </dependency> <!-- Druid 连接池 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-3-starter</artifactId> <version>1.2.25</version> </dependency> <!-- Lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!-- JTS 空间数据处理 --> <dependency> <groupId>org.locationtech.jts</groupId> <artifactId>jts-core</artifactId> <version>1.20.0</version> </dependency> <!-- FastJSON --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>2.0.61</version> </dependency> <!-- GeoTools GeoJSON 输出 --> <dependency> <groupId>org.geotools</groupId> <artifactId>gt-geojson</artifactId> <version>${geotools.version}</version> </dependency> <!-- 分页插件 --> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper-spring-boot-starter</artifactId> <version>2.1.1</version> </dependency> <!-- 工具类 --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> </dependency> <!-- DevTools --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <!-- 测试 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!-- Knife4j API 文档(内置 Swagger) --> <dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId> <version>4.5.0</version> </dependency> </dependencies> <repositories> <repository> <id>osgeo</id> <name>OSGeo Release Repository</name> <url>https://repo.osgeo.org/repository/release/</url> <snapshots><enabled>false</enabled></snapshots> <releases><enabled>true</enabled></releases> </repository> </repositories> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>2. 创建 GeometryModel
路径:src/main/java/com/history/gismap/model/GeometryModel.java
package com.history.gismap.model; import lombok.Getter; import lombok.Setter; import lombok.ToString; import org.locationtech.jts.geom.Geometry; @Getter @Setter @ToString public class GeometryModel { private Integer gId; private String namePy; private String nameCh; private String nameFt; private String presLoc; private String typePy; private String typeCh; private String levRank; private Integer begYr; private String begRule; private Integer endYr; private String endRule; private String geoSrc; private String compiler; private String gecomplr; private String checker; private String entDate; private String begChgTy; private String endChgTy; private Geometry geometry; }3. GeoJSON 转换核心代码
在 Controller 的getGeometry方法中:
GeometryJSON geometryJSON = new GeometryJSON(); for (GeometryModel model : list) { JSONObject item = new JSONObject(); item.put("gId", model.getGId()); item.put("namePy", model.getNamePy()); item.put("nameCh", model.getNameCh()); item.put("nameFt", model.getNameFt()); if (model.getGeometry() != null) { StringWriter writer = new StringWriter(); geometryJSON.write(model.getGeometry(), writer); item.put("geometry", JSONObject.parseObject(writer.toString())); } jsonArray.add(item); }输出json文件演示:
{ "number": 5, "list": [ { "gId": 1, "namePy": "Baoding Fu", "nameCh": "保定府", "nameFt": "...", "geometry": { "type": "MultiPolygon", "coordinates": [[[[116.4, 39.9], ...]]] } } ] }四、启动时把部分表数据加载为本地缓存
1. 创建 GeometryCache
路径:src/main/java/com/history/gismap/service/impl/GeometryCache.java
package com.history.gismap.service.impl; import com.history.gismap.model.GeometryModel; import com.history.gismap.service.MapService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @Slf4j @Service public class GeometryCache implements InitializingBean { @Autowired private MapService mapService; private final Map<String, List<GeometryModel>> cacheMap = new ConcurrentHashMap<>(); @Override public void afterPropertiesSet() { try { List<GeometryModel> allData = mapService.getAllGeometry(); for (GeometryModel model : allData) { String key = model.getNameCh(); if (key == null || key.isEmpty()) { key = "unknown"; } cacheMap.computeIfAbsent(key, k -> new ArrayList<>()).add(model); } log.info("GeometryCache initialized: {} records, {} categories", allData.size(), cacheMap.size()); } catch (Exception e) { log.warn("GeometryCache init failed, starting with empty cache: {}", e.getMessage()); } } public List<GeometryModel> getByCategory(String category) { return cacheMap.get(category); } }关键设计:
| 特性 | 实现 |
|---|---|
| 启动时自动加载 | 实现InitializingBean接口,在afterPropertiesSet()中加载数据 |
| 线程安全 | 使用ConcurrentHashMap存储 |
| 按 category 字段分组 | 用category字段作为 key 分组存储 |
| 优雅降级 | try-catch 包裹,加载失败时使用空缓存,不影响应用启动 |
缓存工作机制:
应用启动 └─ Spring 调用 afterPropertiesSet() └─ mapService.getAllGeometry() → 查全表 └─ 按 category 字段分组 → ConcurrentHashMap<String, List<GeometryModel>> API 请求 └─ GET /history/geometry?category=保定府 └─ geometryCache.getByCategory("保定府") → 直接内存返回 └─ 缓存未命中 → 回退查数据库(WHERE name_ch = #{category})项目完整文件结构
gismap/ ├── pom.xml ├── src/main/ │ ├── java/com/history/gismap/ │ │ ├── GismapApplication.java # 启动类 @MapperScan │ │ ├── controller/ │ │ │ └── MapController.java # REST API 控制器 │ │ ├── dao/ │ │ │ └── MapDao.java # MyBatis Mapper 接口 │ │ ├── model/ │ │ │ ├── PointModel.java # 点数据模型 │ │ │ └── GeometryModel.java # 几何数据模型(含字段) │ │ ├── mybatis/ │ │ │ └── GeometryTypeHandler.java # PostgreSQL geometry ↔ JTS 转换 │ │ └── service/ │ │ ├── MapService.java # 业务接口 │ │ └── impl/ │ │ ├── MapServiceImpl.java # 业务实现 │ │ └── GeometryCache.java # 启动缓存 │ └── resources/ │ ├── application.yml # 主配置 │ ├── logback-boot.xml # 日志配置 │ └── mapper/ │ └── HistoryGISMapper.xml # SQL 映射文件API 端点汇总
| 方法 | 路径 | 参数 | 说明 | 数据来源 |
|---|---|---|---|---|
| GET | /history/pointmodel | gid=1 | 按 ID 查询单条记录,geometry 为 GeoJSON 格式 | 数据库 |
| GET | /history/geometry | category=保定府 | 按地名查询 GeoJSON 数据 | 缓存(优先)→ 数据库(回退) |
| POST | /history/add | PointModel JSON | 新增点位 | 数据库 |
| POST | /history/modify | PointModel JSON | 修改点位 | 数据库 |
| GET | /history/remove | gid=1 | 删除点位 | 数据库 |
使用步骤:
启动项目:
mvn spring-boot:run浏览器打开 Knife4j API 文档页面:http://localhost:8080/doc.html
在文档页面中可看到所有接口的中文说明,点击 "Try it out" 直接测试
或者直接访问:
http://localhost:8080/history/pointmodel?gid=1查询单条http://localhost:8080/history/geometry?category=保德州获取 GeoJSON
Knife4j 页面路径:http://localhost:8080/doc.html(国产增强版 Swagger UI)原生 Swagger 页面路径:http://localhost:8080/swagger-ui/index.html
接口文档
OK,今天就到这了,主要就是构建一下后端,下篇开始写前端,大家心急的可以先看大佬的教程,我们下篇见,拜拜!
该项目并非原创(侵权了私聊删QAQ),而是翻新一个知乎上的大佬的spring boot 2 + vue 2 的项目, 项目教程:知乎的才华横溢吴道简 大佬的教程