#21 GIS 引擎集成: - GeoServer init 脚本(自动创建工作区/数据源) - Leaflet 地图组件 (Vue3 MapView: 点位/弹窗/OSM底图) - GisService: PostGIS 空间查询(附近设备/片区统计/GeoJSON) - GisController: /nearby /device-stats /geojson API #22 IoT 设备接入层: - Kafka Consumer: iot.telemetry + iot.event 消费 - DeviceController: 设备列表/详情/注册/指令下发 REST API #26 消息通知: - NotifyService: 短信/WebSocket/APP Push/多渠道分发 - NotifyController: SMS/Push API #25 DevOps: - 10个微服务 Dockerfile (Eclipse Temurin JRE17) - CI build.sh: Maven构建 + Docker镜像打包 - Frontend Nginx 反向代理配置
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
package com.water.iot.consumer;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.kafka.annotation.KafkaListener;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class IotTelemetryConsumer {
|
||||
|
||||
@KafkaListener(topics = "iot.telemetry", groupId = "wm-iot-consumer")
|
||||
public void consumeTelemetry(String message) {
|
||||
log.debug("Received telemetry: {}", message);
|
||||
// TODO: 解析 json -> 写入 TDengine
|
||||
}
|
||||
|
||||
@KafkaListener(topics = "iot.event", groupId = "wm-iot-consumer")
|
||||
public void consumeEvent(String message) {
|
||||
log.info("Device event: {}", message);
|
||||
// TODO: 处理上下线/故障事件
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.water.iot.controller;
|
||||
|
||||
import com.water.common.core.result.R;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Tag(name = "设备管理")
|
||||
@RestController
|
||||
@RequestMapping("/device")
|
||||
@RequiredArgsConstructor
|
||||
public class DeviceController {
|
||||
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
|
||||
@Operation(summary = "设备列表")
|
||||
@GetMapping("/list")
|
||||
public R<List<Map<String, Object>>> list(@RequestParam(defaultValue = "1") int page,
|
||||
@RequestParam(defaultValue = "10") int size) {
|
||||
int offset = (page - 1) * size;
|
||||
String sql = "SELECT id, device_sn, device_name, device_type, area, status, last_report_time FROM iot_device ORDER BY id LIMIT ? OFFSET ?";
|
||||
return R.ok(jdbcTemplate.queryForList(sql, size, offset));
|
||||
}
|
||||
|
||||
@Operation(summary = "设备详情")
|
||||
@GetMapping("/{id}")
|
||||
public R<Map<String, Object>> getById(@PathVariable Long id) {
|
||||
return R.ok(jdbcTemplate.queryForMap("SELECT * FROM iot_device WHERE id = ?", id));
|
||||
}
|
||||
|
||||
@Operation(summary = "注册设备")
|
||||
@PostMapping
|
||||
public R<String> register(@RequestBody Map<String, Object> body) {
|
||||
jdbcTemplate.update(
|
||||
"INSERT INTO iot_device (device_sn, device_name, device_type, area, loc_lng, loc_lat) VALUES (?,?,?,?,?,?)",
|
||||
body.get("deviceSn"), body.get("deviceName"), body.get("deviceType"), body.get("area"),
|
||||
body.get("lng"), body.get("lat"));
|
||||
return R.ok("注册成功");
|
||||
}
|
||||
|
||||
@Operation(summary = "下发指令")
|
||||
@PostMapping("/{id}/command")
|
||||
public R<String> sendCommand(@PathVariable Long id, @RequestBody Map<String, Object> cmd) {
|
||||
// TODO: 实际指令通过 Kafka -> EMQX -> MQTT -> 设备
|
||||
return R.ok("指令已下发");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.water.iot.controller;
|
||||
|
||||
import com.water.common.core.result.R;
|
||||
import com.water.iot.service.GisService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Tag(name = "GIS 地图服务")
|
||||
@RestController
|
||||
@RequestMapping("/gis")
|
||||
@RequiredArgsConstructor
|
||||
public class GisController {
|
||||
|
||||
private final GisService gisService;
|
||||
|
||||
@Operation(summary = "查询附近设备")
|
||||
@GetMapping("/nearby")
|
||||
public R<List<Map<String, Object>>> nearby(@RequestParam double lng, @RequestParam double lat,
|
||||
@RequestParam(defaultValue = "5") double radius) {
|
||||
return R.ok(gisService.findDevicesNearby(lng, lat, radius));
|
||||
}
|
||||
|
||||
@Operation(summary = "片区设备统计")
|
||||
@GetMapping("/device-stats")
|
||||
public R<List<Map<String, Object>>> deviceStats() {
|
||||
return R.ok(gisService.getDeviceStatsByArea());
|
||||
}
|
||||
|
||||
@Operation(summary = "在线设备 GeoJSON")
|
||||
@GetMapping("/geojson")
|
||||
public R<String> geojson() {
|
||||
return R.ok(gisService.getOnlineDevicesGeoJson());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package com.water.iot.service;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class GisService {
|
||||
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
|
||||
/**
|
||||
* 查询指定半径内的设备
|
||||
*/
|
||||
public List<Map<String, Object>> findDevicesNearby(double lng, double lat, double radiusKm) {
|
||||
String sql = """
|
||||
SELECT id, device_sn, device_name, device_type, area,
|
||||
ST_Distance(geom::geography, ST_SetSRID(ST_MakePoint(?, ?), 4326)::geography) / 1000 AS distance_km,
|
||||
ST_X(geom) as lng, ST_Y(geom) as lat
|
||||
FROM iot_device
|
||||
WHERE ST_DWithin(geom::geography, ST_SetSRID(ST_MakePoint(?, ?), 4326)::geography, ? * 1000)
|
||||
AND status = 'online'
|
||||
ORDER BY distance_km
|
||||
""";
|
||||
return jdbcTemplate.queryForList(sql, lng, lat, lng, lat, radiusKm);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询片区内的设备统计
|
||||
*/
|
||||
public List<Map<String, Object>> getDeviceStatsByArea() {
|
||||
String sql = """
|
||||
SELECT area, device_type, COUNT(*) as count
|
||||
FROM iot_device
|
||||
WHERE deleted = 0
|
||||
GROUP BY area, device_type
|
||||
ORDER BY area
|
||||
""";
|
||||
return jdbcTemplate.queryForList(sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有在线设备 GeoJSON
|
||||
*/
|
||||
public String getOnlineDevicesGeoJson() {
|
||||
String sql = """
|
||||
SELECT json_build_object(
|
||||
'type', 'FeatureCollection',
|
||||
'features', json_agg(json_build_object(
|
||||
'type', 'Feature',
|
||||
'geometry', ST_AsGeoJSON(geom)::json,
|
||||
'properties', json_build_object(
|
||||
'id', id, 'name', device_name, 'type', device_type,
|
||||
'sn', device_sn, 'area', area, 'status', status
|
||||
)
|
||||
))
|
||||
) AS geojson
|
||||
FROM iot_device WHERE status = 'online' AND geom IS NOT NULL
|
||||
""";
|
||||
return jdbcTemplate.queryForObject(sql, String.class);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user