diff --git a/deploy-test.sh b/deploy-test.sh
new file mode 100755
index 0000000..bfea996
--- /dev/null
+++ b/deploy-test.sh
@@ -0,0 +1,68 @@
+#!/bin/bash
+set -e
+
+# ==================== 配置 ====================
+SERVER_HOST="172.17.16.14"
+SERVER_USER="root"
+SERVER_PASS="JCSS@2025!@#$"
+UPLOAD_DIR="/home/user/test_jar"
+RESTART_SCRIPT="restart.sh"
+JAR_NAME="urbanops-server.jar"
+PROJECT_DIR="$(cd "$(dirname "$0")" && pwd)"
+JAR_PATH="$PROJECT_DIR/urbanops-server/target/$JAR_NAME"
+
+# ==================== 函数 ====================
+
+check_sshpass() {
+ if ! command -v sshpass &> /dev/null; then
+ echo "[check] sshpass 未安装,尝试安装..."
+ if command -v brew &> /dev/null; then
+ brew install hudochenkov/sshpass/sshpass
+ else
+ echo "[ERROR] 请手动安装 sshpass: brew install hudochenkov/sshpass/sshpass"
+ exit 1
+ fi
+ fi
+}
+
+build() {
+ echo "[build] 开始 Maven 打包(跳过测试)..."
+ cd "$PROJECT_DIR"
+ mvn clean package -DskipTests
+ if [ -f "$JAR_PATH" ]; then
+ echo "[build] 打包完成: $JAR_PATH ($(du -sh "$JAR_PATH" | cut -f1))"
+ else
+ echo "[ERROR] 打包失败,未找到 $JAR_PATH"
+ exit 1
+ fi
+}
+
+upload() {
+ echo "[upload] 上传 JAR 到 $SERVER_HOST:$UPLOAD_DIR/ ..."
+ sshpass -p "$SERVER_PASS" scp -o StrictHostKeyChecking=no "$JAR_PATH" "$SERVER_USER@$SERVER_HOST:$UPLOAD_DIR/"
+ echo "[upload] 上传完成"
+}
+
+restart() {
+ echo "[restart] 调用远程重启脚本..."
+ sshpass -p "$SERVER_PASS" ssh -o StrictHostKeyChecking=no "$SERVER_USER@$SERVER_HOST" \
+ "cd $UPLOAD_DIR && chmod +x $RESTART_SCRIPT && ./$RESTART_SCRIPT"
+ echo "[restart] 远程重启完成"
+}
+
+# ==================== 主流程 ====================
+
+echo "========================================"
+echo " UrbanOps 测试环境部署"
+echo " 目标: $SERVER_USER@$SERVER_HOST:$UPLOAD_DIR"
+echo "========================================"
+
+check_sshpass
+build
+upload
+restart
+
+echo ""
+echo "========================================"
+echo " 部署完成"
+echo "========================================"
diff --git a/pom.xml b/pom.xml
index c23e6ca..74308ca 100644
--- a/pom.xml
+++ b/pom.xml
@@ -20,7 +20,7 @@
urbanops-module-workorder
urbanops-module-bpm
- urbanops-module-report
+
urbanops-module-api
diff --git a/script/convert_gis_road_coords.py b/script/convert_gis_road_coords.py
new file mode 100644
index 0000000..879e8f5
--- /dev/null
+++ b/script/convert_gis_road_coords.py
@@ -0,0 +1,177 @@
+#!/usr/bin/env python3
+"""
+WGS84 → GCJ02(高德坐标系)坐标转换脚本
+目标表: garden_gis_road
+转换字段: starting_latitude/longitude, end_latitude/longitude, gis_polygon_coords
+
+使用方法:
+ python3 convert_gis_road_coords.py
+
+前提: pip install pymysql
+"""
+import pymysql
+import math
+import re
+
+# ========== 数据库配置 ==========
+DB_CONFIG = {
+ 'host': '172.17.16.15',
+ 'port': 3306,
+ 'user': 'root',
+ 'password': 'mysql2025!',
+ 'database': 'urban_ops_agent',
+ 'charset': 'utf8mb4',
+}
+
+# ========== WGS84 → GCJ02 算法 ==========
+PI = math.pi
+A = 6378245.0 # 长半轴
+EE = 0.00669342162296594323 # 偏心率平方
+
+
+def wgs84_to_gcj02(lng, lat):
+ """WGS84 转 GCJ02(高德坐标系)"""
+ if lng < 72.004 or lng > 137.8347 or lat < 0.8293 or lat > 55.8271:
+ return lng, lat
+
+ x, y = lng - 105.0, lat - 35.0
+
+ dlat = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * math.sqrt(abs(x))
+ dlat += (20.0 * math.sin(6.0 * x * PI) + 20.0 * math.sin(2.0 * x * PI)) * 2.0 / 3.0
+ dlat += (20.0 * math.sin(y * PI) + 40.0 * math.sin(y / 3.0 * PI)) * 2.0 / 3.0
+ dlat += (160.0 * math.sin(y / 12.0 * PI) + 320.0 * math.sin(y * PI / 30.0)) * 2.0 / 3.0
+
+ dlng = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * math.sqrt(abs(x))
+ dlng += (20.0 * math.sin(6.0 * x * PI) + 20.0 * math.sin(2.0 * x * PI)) * 2.0 / 3.0
+ dlng += (20.0 * math.sin(x * PI) + 40.0 * math.sin(x / 3.0 * PI)) * 2.0 / 3.0
+ dlng += (150.0 * math.sin(x / 12.0 * PI) + 300.0 * math.sin(x / 30.0 * PI)) * 2.0 / 3.0
+
+ rad_lat = lat / 180.0 * PI
+ magic = math.sin(rad_lat)
+ magic = 1 - EE * magic * magic
+ sqrt_magic = math.sqrt(magic)
+
+ dlat = (dlat * 180.0) / ((A * (1 - EE)) / (magic * sqrt_magic) * PI)
+ dlng = (dlng * 180.0) / (A / sqrt_magic * math.cos(rad_lat) * PI)
+
+ return lng + dlng, lat + dlat
+
+
+def transform_wkt(wkt):
+ """转换 WKT 字符串中的所有坐标对(支持 POLYGON / MULTIPOLYGON)"""
+ if not wkt or not wkt.strip():
+ return wkt
+
+ def convert_pair(m):
+ nlng, nlat = wgs84_to_gcj02(float(m.group(1)), float(m.group(2)))
+ return f"{nlng:.6f} {nlat:.6f}"
+
+ return re.sub(r'(\d+\.?\d*)\s+(\d+\.?\d*)', convert_pair, wkt)
+
+
+def main():
+ conn = pymysql.connect(**DB_CONFIG)
+ cur = conn.cursor()
+
+ # 1. 转换起点/终点坐标(如果有的话)
+ cur.execute(
+ "SELECT id, starting_latitude, starting_longitude, end_latitude, end_longitude "
+ "FROM garden_gis_road "
+ "WHERE (starting_latitude IS NOT NULL AND starting_latitude != '') "
+ " OR (end_latitude IS NOT NULL AND end_latitude != '')"
+ )
+ rows = cur.fetchall()
+ updated = 0
+ for row in rows:
+ id_, slat, slng, elat, elng = row
+ updates = []
+ params = []
+
+ if slat and slng and slat.strip():
+ try:
+ nlng, nlat = wgs84_to_gcj02(float(slng), float(slat))
+ updates.append('starting_longitude = %s')
+ updates.append('starting_latitude = %s')
+ params.append(str(round(nlng, 6)))
+ params.append(str(round(nlat, 6)))
+ except ValueError:
+ pass
+
+ if elat and elng and elat.strip():
+ try:
+ nlng, nlat = wgs84_to_gcj02(float(elng), float(elat))
+ updates.append('end_longitude = %s')
+ updates.append('end_latitude = %s')
+ params.append(str(round(nlng, 6)))
+ params.append(str(round(nlat, 6)))
+ except ValueError:
+ pass
+
+ if updates:
+ sql = 'UPDATE garden_gis_road SET ' + ', '.join(updates) + ' WHERE id = %s'
+ params.append(id_)
+ cur.execute(sql, params)
+ updated += 1
+
+ conn.commit()
+ print(f'起点/终点坐标: 转换 {updated} 条')
+
+ # 2. 转换围栏坐标
+ cur.execute(
+ "SELECT COUNT(*) FROM garden_gis_road "
+ "WHERE gis_polygon_coords IS NOT NULL AND gis_polygon_coords != ''"
+ )
+ total = cur.fetchone()[0]
+ print(f'围栏坐标记录: {total} 条')
+
+ batch_size = 500
+ updated = 0
+ for offset in range(0, total, batch_size):
+ cur.execute(
+ "SELECT id, gis_polygon_coords FROM garden_gis_road "
+ "WHERE gis_polygon_coords IS NOT NULL AND gis_polygon_coords != '' "
+ "LIMIT %s OFFSET %s",
+ (batch_size, offset)
+ )
+ for id_, wkt in cur.fetchall():
+ new_wkt = transform_wkt(wkt)
+ if new_wkt != wkt:
+ cur.execute(
+ "UPDATE garden_gis_road SET gis_polygon_coords = %s WHERE id = %s",
+ (new_wkt, id_)
+ )
+ updated += 1
+ conn.commit()
+ print(f' 进度: {min(offset + batch_size, total)}/{total}, 已更新: {updated}')
+
+ print(f'围栏坐标: 转换 {updated} 条')
+
+ # 3. 验证
+ cur.execute(
+ "SELECT COUNT(*) FROM garden_gis_road "
+ "WHERE gis_polygon_coords IS NOT NULL AND gis_polygon_coords != ''"
+ )
+ total = cur.fetchone()[0]
+ unconverted = 0
+ cur.execute(
+ "SELECT id, gis_polygon_coords FROM garden_gis_road "
+ "WHERE gis_polygon_coords IS NOT NULL AND gis_polygon_coords != ''"
+ )
+ for id_, wkt in cur.fetchall():
+ m = re.search(r'(\d+\.?\d*)\s+(\d+\.?\d*)', wkt)
+ if m:
+ wlng, wlat = float(m.group(1)), float(m.group(2))
+ nlng, nlat = wgs84_to_gcj02(wlng, wlat)
+ if abs(wlng - nlng) < 0.00001:
+ unconverted += 1
+
+ print(f'\n验证: {unconverted}/{total} 条未转换')
+ if unconverted == 0:
+ print('所有坐标已成功转换为 GCJ02(高德坐标系)')
+
+ cur.close()
+ conn.close()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/script/shell/deploy.sh b/script/shell/deploy.sh
index 1f7a314..4d2a94e 100644
--- a/script/shell/deploy.sh
+++ b/script/shell/deploy.sh
@@ -1,5 +1,5 @@
#!/bin/bash
-set -e
+set -eo pipefail
DATE=$(date +%Y%m%d%H%M)
# 基础路径
@@ -9,9 +9,9 @@ SOURCE_PATH=$BASE_PATH/build
# 服务名称。同时约定部署服务的 jar 包名字也为它。
SERVER_NAME=urbanops-server
# 环境
-PROFILES_ACTIVE=development
+PROFILES_ACTIVE=dev
# 健康检查 URL
-HEALTH_CHECK_URL=http://127.0.0.1:48080/actuator/health/
+HEALTH_CHECK_URL=http://127.0.0.1:48081/actuator/health/
# heapError 存放路径
HEAP_ERROR_PATH=$BASE_PATH/heapError
@@ -33,7 +33,8 @@ function backup() {
# 如果存在,则备份到 backup 目录下,使用时间作为后缀
else
echo "[backup] 开始备份 $SERVER_NAME ..."
- cp $BASE_PATH/$SERVER_NAME.jar $BASE_PATH/backup/$SERVER_NAME-$DATE.jar
+ mkdir -p "$BASE_PATH/backup"
+ cp "$BASE_PATH/$SERVER_NAME.jar" "$BASE_PATH/backup/$SERVER_NAME-$DATE.jar"
echo "[backup] 备份 $SERVER_NAME 完成"
fi
}
@@ -47,12 +48,12 @@ function transfer() {
echo "[transfer] $BASE_PATH/$SERVER_NAME.jar 不存在,跳过删除"
else
echo "[transfer] 移除 $BASE_PATH/$SERVER_NAME.jar 完成"
- rm $BASE_PATH/$SERVER_NAME.jar
+ rm "$BASE_PATH/$SERVER_NAME.jar"
fi
# 复制新 jar 包
echo "[transfer] 从 $SOURCE_PATH 中获取 $SERVER_NAME.jar 并迁移至 $BASE_PATH ...."
- cp $SOURCE_PATH/$SERVER_NAME.jar $BASE_PATH
+ cp "$SOURCE_PATH/$SERVER_NAME.jar" "$BASE_PATH"
echo "[transfer] 转移 $SERVER_NAME.jar 完成"
}
@@ -60,7 +61,7 @@ function transfer() {
# 停止:优雅关闭之前已经启动的服务
function stop() {
echo "[stop] 开始停止 $BASE_PATH/$SERVER_NAME"
- PID=$(ps -ef | grep $BASE_PATH/$SERVER_NAME | grep -v "grep" | awk '{print $2}')
+ PID=$(ps -ef | grep "$BASE_PATH/$SERVER_NAME.jar" | grep -v "grep" | awk '{print $2}')
# 如果 Java 服务启动中,则进行关闭
if [ -n "$PID" ]; then
# 正常关闭
@@ -70,7 +71,7 @@ function stop() {
for ((i = 0; i < 120; i++))
do
sleep 1
- PID=$(ps -ef | grep $BASE_PATH/$SERVER_NAME | grep -v "grep" | awk '{print $2}')
+ PID=$(ps -ef | grep "$BASE_PATH/$SERVER_NAME.jar" | grep -v "grep" | awk '{print $2}')
if [ -n "$PID" ]; then
echo -e ".\c"
else
@@ -99,7 +100,8 @@ function start() {
echo "[start] PROFILES: $PROFILES_ACTIVE"
# 开始启动
- BUILD_ID=dontKillMe nohup java -server $JAVA_OPS $JAVA_AGENT -jar $BASE_PATH/$SERVER_NAME.jar --spring.profiles.active=$PROFILES_ACTIVE &
+ mkdir -p "$HEAP_ERROR_PATH"
+ nohup java -server $JAVA_OPS $JAVA_AGENT -jar "$BASE_PATH/$SERVER_NAME.jar" --spring.profiles.active=$PROFILES_ACTIVE >> "$BASE_PATH/nohup.out" 2>&1 &
echo "[start] 启动 $BASE_PATH/$SERVER_NAME 完成"
}
@@ -112,7 +114,7 @@ function healthCheck() {
for ((i = 0; i < 120; i++))
do
# 请求健康检查地址,只获取状态码。
- result=`curl -I -m 10 -o /dev/null -s -w %{http_code} $HEALTH_CHECK_URL || echo "000"`
+ result=$(curl -I -m 10 -o /dev/null -s -w %{http_code} "$HEALTH_CHECK_URL" || echo "000")
# 如果状态码为 200,则说明健康检查通过
if [ "$result" == "200" ]; then
echo "[healthCheck] 健康检查通过";
@@ -127,24 +129,29 @@ function healthCheck() {
# 健康检查未通过,则异常退出 shell 脚本,不继续部署。
if [ ! "$result" == "200" ]; then
echo "[healthCheck] 健康检查不通过,可能部署失败。查看日志,自行判断是否启动成功";
- tail -n 10 nohup.out
+ tail -n 10 "$BASE_PATH/nohup.out"
exit 1;
# 健康检查通过,打印最后 10 行日志,可能部署的人想看下日志。
else
- tail -n 10 nohup.out
+ tail -n 10 "$BASE_PATH/nohup.out"
fi
# 如果未配置健康检查,则 sleep 120 秒,人工看日志是否部署成功。
else
echo "[healthCheck] HEALTH_CHECK_URL 未配置,开始 sleep 120 秒";
sleep 120
echo "[healthCheck] sleep 120 秒完成,查看日志,自行判断是否启动成功";
- tail -n 50 nohup.out
+ tail -n 50 "$BASE_PATH/nohup.out"
fi
}
# 部署
function deploy() {
- cd $BASE_PATH
+ cd "$BASE_PATH" || { echo "[deploy] 错误:$BASE_PATH 目录不存在"; exit 1; }
+ if [ ! -d "$SOURCE_PATH" ]; then
+ echo "[deploy] 错误:构建目录 $SOURCE_PATH 不存在"
+ exit 1
+ fi
+ mkdir -p "$BASE_PATH/backup" "$HEAP_ERROR_PATH"
# 备份原 jar
backup
# 停止 Java 服务
diff --git a/sql/dis-check-menu.sql b/sql/dis-check-menu.sql
new file mode 100644
index 0000000..8b3ea59
--- /dev/null
+++ b/sql/dis-check-menu.sql
@@ -0,0 +1,74 @@
+-- =====================================================
+-- dis核对 完整部署脚本
+-- 数据库: urban_ops_agent
+-- 包含: 表结构变更 + 菜单创建 + 角色授权
+-- 执行方式: 在对应环境的数据库中执行此 SQL
+-- =====================================================
+
+-- ==========================================
+-- Part 0: garden_gis_road 表结构变更
+-- ==========================================
+-- 新增多边形围栏边界坐标字段(MySQL 8.x 兼容写法)
+SET @col_exists = (
+ SELECT COUNT(*) FROM information_schema.COLUMNS
+ WHERE TABLE_SCHEMA = DATABASE()
+ AND TABLE_NAME = 'garden_gis_road'
+ AND COLUMN_NAME = 'gis_polygon_coords'
+);
+
+SET @sql = IF(@col_exists = 0,
+ 'ALTER TABLE garden_gis_road ADD COLUMN gis_polygon_coords LONGTEXT NOT NULL COMMENT ''多边形/矩形围栏边界坐标,格式:POLYGON((x1 y1, x2 y2, ..., xn yn, x1 y1)) 存储的是WGS84坐标系''',
+ 'SELECT ''COLUMN gis_polygon_coords already exists'' AS info'
+);
+PREPARE stmt FROM @sql;
+EXECUTE stmt;
+DEALLOCATE PREPARE stmt;
+
+-- ==========================================
+-- Part 1: 菜单创建
+-- ==========================================
+
+-- 1. 新增「dis核对」子菜单(位于电子围栏下)
+INSERT INTO system_menu
+(name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
+VALUES
+('dis核对', '', 2, 1, 5698, 'disCheck', 'ep:view', 'fence/dis-check/index', 'DisCheck', 0, 1, 0, 1, '1', NOW(), '1', NOW(), 0);
+
+-- 2. 获取上一步插入的菜单ID,用于后续按钮权限插入
+SET @dis_check_menu_id = LAST_INSERT_ID();
+
+-- 3. 新增「导出」按钮权限(挂在新菜单下)
+INSERT INTO system_menu
+(name, permission, type, sort, parent_id, path, icon, component, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
+VALUES
+('导出', 'garden:gis-road:export', 3, 0, @dis_check_menu_id, '', '', '', 0, 1, 0, 1, '1', NOW(), '1', NOW(), 0);
+
+-- 4. 新增「查询」按钮权限
+INSERT INTO system_menu
+(name, permission, type, sort, parent_id, path, icon, component, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
+VALUES
+('查询', 'garden:gis-road:query', 3, 1, @dis_check_menu_id, '', '', '', 0, 1, 0, 1, '1', NOW(), '1', NOW(), 0);
+
+-- 5. 给超级管理员(role_id=1)分配菜单权限
+INSERT INTO system_role_menu (role_id, menu_id, creator, create_time, updater, update_time)
+VALUES
+(1, @dis_check_menu_id, '1', NOW(), '1', NOW());
+
+INSERT INTO system_role_menu (role_id, menu_id, creator, create_time, updater, update_time)
+SELECT 1, id, '1', NOW(), '1', NOW()
+FROM system_menu
+WHERE parent_id = @dis_check_menu_id AND deleted = 0;
+
+-- =====================================================
+-- 验证插入结果
+-- =====================================================
+SELECT id, name, type, parent_id, path, component, permission
+FROM system_menu
+WHERE parent_id = 5698 OR id = @dis_check_menu_id
+ORDER BY parent_id, sort;
+
+SELECT rm.role_id, rm.menu_id, m.name, m.permission
+FROM system_role_menu rm
+JOIN system_menu m ON rm.menu_id = m.id
+WHERE rm.menu_id = @dis_check_menu_id
+ OR rm.menu_id IN (SELECT id FROM system_menu WHERE parent_id = @dis_check_menu_id);
diff --git a/sql/garden_road_add_gis_fields.sql b/sql/garden_road_add_gis_fields.sql
new file mode 100644
index 0000000..7673298
--- /dev/null
+++ b/sql/garden_road_add_gis_fields.sql
@@ -0,0 +1,5 @@
+-- garden_road 表新增 GIS 关联字段
+ALTER TABLE garden_road
+ ADD COLUMN gis_plot_name VARCHAR(200) DEFAULT NULL COMMENT 'GIS 图斑名称',
+ ADD COLUMN gis_plot_area FLOAT DEFAULT NULL COMMENT 'GIS 图斑面积',
+ ADD COLUMN gis_is_related TINYINT DEFAULT 0 COMMENT '是否关联GIS (0=未关联, 1=已关联)';
diff --git a/sql/sync_land_forest_to_road.sql b/sql/sync_land_forest_to_road.sql
new file mode 100644
index 0000000..011fc63
--- /dev/null
+++ b/sql/sync_land_forest_to_road.sql
@@ -0,0 +1,59 @@
+-- ============================================================
+-- 将 garden_gis_land_forest_data 数据同步到 garden_gis_road
+-- 按 greenname + greentype 分组聚合
+-- - xbmj 求和 → gis_plot_area
+-- - geometry 所有 MULTIPOLYGON 环拼接 → gis_polygon_coords (WKT)
+-- - 其他 gis_ 字段取分组内 MAX 值
+--
+-- 注意:MySQL 不支持 ST_Union 聚合函数,geometry 并集通过
+-- GROUP_CONCAT 拼接各 MULTIPOLYGON 的内部环来实现,不做空间融合。
+-- 如需真正融合重叠区域,建议在 Java 层用 JTS 处理。
+-- ============================================================
+
+-- 避免 GROUP_CONCAT 默认 1024 字节限制导致 WKT 截断
+SET SESSION group_concat_max_len = 10485760; -- 10MB
+
+INSERT INTO garden_gis_road (
+ gis_plot_name,
+ gis_green_type_name,
+ gis_plot_area,
+ gis_polygon_coords,
+ gis_owner_unit,
+ gis_property_unit,
+ gis_manage_unit,
+ gis_street,
+ gis_plot_code,
+ gis_green_type_code,
+ street_id,
+ company_id,
+ dept_id
+)
+SELECT
+ greenname AS gis_plot_name,
+ greentype AS gis_green_type_name,
+ ROUND(SUM(xbmj), 2) AS gis_plot_area,
+ CONCAT(
+ 'MULTIPOLYGON(',
+ GROUP_CONCAT(
+ SUBSTR(
+ ST_AsText(geometry),
+ 14, -- 跳过 'MULTIPOLYGON('(13字符)
+ LENGTH(ST_AsText(geometry)) - 14 -- 去掉首尾各1个括号
+ )
+ SEPARATOR ','
+ ),
+ ')'
+ ) AS gis_polygon_coords,
+ MAX(cqdw) AS gis_owner_unit,
+ MAX(cqdw) AS gis_property_unit,
+ MAX(gl_dw) AS gis_manage_unit,
+ MAX(CONCAT_WS('-', sheng, shi, xian, xiang)) AS gis_street,
+ '' AS gis_plot_code,
+ '' AS gis_green_type_code,
+ '' AS street_id,
+ 0 AS company_id,
+ 0 AS dept_id
+FROM garden_gis_land_forest_data
+WHERE greenname IS NOT NULL
+ AND greentype IS NOT NULL
+GROUP BY greenname, greentype;
diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/gis/GisRoadController.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/gis/GisRoadController.java
new file mode 100644
index 0000000..cb7cc6a
--- /dev/null
+++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/gis/GisRoadController.java
@@ -0,0 +1,114 @@
+package com.zteits.urbanops.module.garden.controller.admin.gis;
+
+import com.zteits.urbanops.framework.common.pojo.PageParam;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.web.bind.annotation.*;
+import jakarta.annotation.Resource;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.security.access.prepost.PreAuthorize;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.Operation;
+
+import jakarta.validation.*;
+import jakarta.servlet.http.*;
+import java.io.IOException;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.*;
+
+import com.zteits.urbanops.framework.common.pojo.PageResult;
+import com.zteits.urbanops.framework.common.pojo.CommonResult;
+import static com.zteits.urbanops.framework.common.pojo.CommonResult.success;
+
+import com.zteits.urbanops.framework.excel.core.util.ExcelUtils;
+import com.zteits.urbanops.framework.apilog.core.annotation.ApiAccessLog;
+import static com.zteits.urbanops.framework.apilog.core.enums.OperateTypeEnum.*;
+
+import com.zteits.urbanops.module.garden.controller.admin.gis.vo.*;
+import com.zteits.urbanops.module.garden.service.gis.GisRoadService;
+
+@Tag(name = "管理后台 - GIS 道路管理")
+@RestController
+@RequestMapping("/garden/gis/road")
+@Validated
+public class GisRoadController {
+ private static final Logger log = LoggerFactory.getLogger(GisRoadController.class);
+
+ @Resource
+ private GisRoadService gisRoadService;
+
+ @GetMapping("/list")
+ @Operation(summary = "获得 GIS 道路分页列表")
+ @PreAuthorize("@ss.hasPermission('garden:gis-road:query')")
+ public CommonResult> getGisRoadList(@Valid GisRoadPageReqVO pageReqVO) {
+ PageResult pageResult = gisRoadService.getGisRoadPage(pageReqVO);
+ return success(pageResult);
+ }
+
+ @GetMapping("/list-all")
+ @Operation(summary = "获得全部 GIS 道路列表(不分页,用于地图渲染)")
+ @PreAuthorize("@ss.hasPermission('garden:gis-road:query')")
+ public CommonResult> getGisRoadAllList(GisRoadPageReqVO reqVO) {
+ return success(gisRoadService.getGisRoadAllList(reqVO));
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "获得 GIS 道路详情")
+ @Parameter(name = "id", description = "编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('garden:gis-road:query')")
+ public CommonResult getGisRoad(@RequestParam("id") Long id) {
+ return success(gisRoadService.getGisRoad(id));
+ }
+
+ @GetMapping("/export-excel")
+ @Operation(summary = "导出 GIS 道路 Excel")
+ @PreAuthorize("@ss.hasPermission('garden:gis-road:export')")
+ @ApiAccessLog(operateType = EXPORT)
+ public void exportGisRoadExcel(@Valid GisRoadPageReqVO pageReqVO,
+ HttpServletResponse response) throws IOException {
+ pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
+ List list = gisRoadService.getGisRoadPage(pageReqVO).getList();
+ // 只导出表格展示的 6 列数据
+ List exportList = new ArrayList<>();
+ for (GisRoadRespVO vo : list) {
+ GisRoadExportVO exportVO = new GisRoadExportVO();
+ exportVO.setGisPlotName(vo.getGisPlotName());
+ exportVO.setRoadName(vo.getRoadName());
+ exportVO.setGisPlotArea(vo.getGisPlotArea());
+ exportVO.setTotalArea(vo.getTotalArea());
+ exportVO.setGisIsRelatedStr(vo.getGisIsRelated() != null && vo.getGisIsRelated() == 1 ? "已关联" : "未关联");
+ exportVO.setGisDiffArea(vo.getGisDiffArea());
+ exportList.add(exportVO);
+ }
+ String filename = "GIS核对" + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")) + ".xls";
+ ExcelUtils.write(response, filename, "数据", GisRoadExportVO.class, exportList);
+ }
+
+ @PostMapping("/associate")
+ @Operation(summary = "关联道路到 GIS 图斑")
+ @PreAuthorize("@ss.hasPermission('garden:gis-road:update')")
+ public CommonResult associateRoad(@Valid @RequestBody GisRoadAssociateReqVO reqVO) {
+ gisRoadService.associateRoad(reqVO);
+ return success(true);
+ }
+
+ @PostMapping("/disassociate")
+ @Operation(summary = "取消 GIS 图斑与道路的关联")
+ @PreAuthorize("@ss.hasPermission('garden:gis-road:update')")
+ public CommonResult disassociateRoad(@Valid @RequestBody GisRoadDisassociateReqVO reqVO) {
+ gisRoadService.disassociateRoad(reqVO);
+ return success(true);
+ }
+
+ @PostMapping("/convert-coordinates")
+ @Operation(summary = "将全部道路坐标从 WGS84 转换为 GCJ02(高德坐标系)")
+ @PreAuthorize("@ss.hasPermission('garden:gis-road:update')")
+ public CommonResult convertCoordinates() {
+ int count = gisRoadService.convertCoordinatesToGcj02();
+ log.info("坐标转换完成,共转换 {} 条记录", count);
+ return success("坐标转换完成,共转换 " + count + " 条记录");
+ }
+
+}
diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/gis/GisTypeController.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/gis/GisTypeController.java
new file mode 100644
index 0000000..cba4017
--- /dev/null
+++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/gis/GisTypeController.java
@@ -0,0 +1,55 @@
+package com.zteits.urbanops.module.garden.controller.admin.gis;
+
+import io.swagger.v3.oas.annotations.tags.Tag;
+import io.swagger.v3.oas.annotations.Operation;
+import org.springframework.web.bind.annotation.*;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import java.util.*;
+
+import com.zteits.urbanops.framework.common.pojo.CommonResult;
+import static com.zteits.urbanops.framework.common.pojo.CommonResult.success;
+import com.zteits.urbanops.module.garden.dal.dataobject.gis.GisTypeDO;
+import com.zteits.urbanops.module.garden.service.gis.GisTypeService;
+
+@Tag(name = "管理后台 - GIS 绿地类型")
+@RestController
+@RequestMapping("/garden/gis/type")
+public class GisTypeController {
+
+ @Resource
+ private GisTypeService gisTypeService;
+
+ @GetMapping("/list-by-parent")
+ @Operation(summary = "按父级编码查子类型(级联下拉用)")
+ public CommonResult>> listByParentCode(
+ @RequestParam(required = false) String parentCode) {
+ List list = gisTypeService.listByParentCode(parentCode);
+ List