news 2026/5/2 1:42:55

记录我的webgis项目(3):从零开始,构建电子地图网站 spring boot3 +vue3 + leaflet之后端搭建(2)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
记录我的webgis项目(3):从零开始,构建电子地图网站 spring boot3 +vue3 + leaflet之后端搭建(2)

(依旧叠甲,我也是菜鸟,悉知我的功力还很浅薄,如果有哪里做的不行欢迎批评指正,大部分代码都是借助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 字段要关联GeometryTypeHandler

  • INSERT/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.typedruid-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 → 数据库setNonNullParameterPGobject包装 Geometry 的 WKT 字符串,type设为"geometry"
数据库 → JavaconvertToGeometryPGobject取出 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/pointmodelgid=1按 ID 查询单条记录,geometry 为 GeoJSON 格式数据库
GET/history/geometrycategory=保定府按地名查询 GeoJSON 数据缓存(优先)→ 数据库(回退)
POST/history/addPointModel JSON新增点位数据库
POST/history/modifyPointModel JSON修改点位数据库
GET/history/removegid=1删除点位数据库

使用步骤:

  1. 启动项目:mvn spring-boot:run

  2. 浏览器打开 Knife4j API 文档页面:http://localhost:8080/doc.html

  3. 在文档页面中可看到所有接口的中文说明,点击 "Try it out" 直接测试

  4. 或者直接访问:

    • 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 的项目, 项目教程:知乎的才华横溢吴道简 大佬的教程

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/2 1:39:25

通过用量看板清晰观测团队AI模型成本与消耗趋势

通过用量看板清晰观测团队AI模型成本与消耗趋势 1. 用量看板的核心价值 在团队协作开发场景中&#xff0c;AI模型调用往往分散在不同项目、不同成员之间。传统模式下&#xff0c;管理者难以直观掌握整体资源消耗情况&#xff0c;容易导致预算超支或资源分配不均。Taotoken的用…

作者头像 李华
网站建设 2026/5/2 1:37:24

同样是 30B 级大模型:为啥的能写全栈,有的连狼羊菜都翻车

前言&#xff1a;最近我发现一个很有意思的现象&#xff1a;现在很多大模型&#xff0c;已经不是“会不会回答”的差距&#xff0c;而是“能不能把活干完”的差距。 有的模型回复很快&#xff0c;看起来很聪明&#xff0c;但一到经典逻辑题&#xff0c;第一步就翻车。有的模型推…

作者头像 李华
网站建设 2026/5/2 1:33:25

B站Index-1.9B:轻量级文本嵌入模型原理、部署与RAG实战

1. 项目概述&#xff1a;一个轻量级但能打的文本嵌入模型最近在折腾RAG&#xff08;检索增强生成&#xff09;和智能问答系统时&#xff0c;我又一次被向量检索的效率和精度问题给卡住了。市面上主流的文本嵌入模型&#xff0c;比如OpenAI的text-embedding-ada-002&#xff0c;…

作者头像 李华
网站建设 2026/5/2 1:24:24

Android AI聚合聊天应用RikkaHub:原生开发与架构设计全解析

1. 项目概述&#xff1a;一个原生Android LLM聚合聊天客户端 如果你和我一样&#xff0c;在手机上同时用着好几个AI助手——比如需要OpenAI的GPT-4o来处理复杂逻辑&#xff0c;用Claude来写长文&#xff0c;用DeepSeek来查代码&#xff0c;偶尔还想试试本地部署的Ollama模型——…

作者头像 李华