package com.ruoyi.web.controller; import java.util.List; import javax.servlet.http.HttpServletResponse; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import com.ruoyi.common.annotation.Log; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.enums.BusinessType; import com.ruoyi.system.domain.ObjectOperationLog; import com.ruoyi.system.domain.Routes; import com.ruoyi.system.service.IObjectOperationLogService; import com.ruoyi.system.service.IRoutesService; import com.ruoyi.common.utils.poi.ExcelUtil; import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.system.domain.dto.PlatformStyleDTO; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONObject; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.beans.factory.annotation.Qualifier; import java.util.ArrayList; import java.util.Set; /** * 实体部署与航线Controller * * @author ruoyi * @date 2026-01-14 */ @RestController @RequestMapping("/system/routes") public class RoutesController extends BaseController { @Autowired private IRoutesService routesService; @Autowired private IObjectOperationLogService objectOperationLogService; @Autowired private RedisTemplate redisTemplate; @Autowired @Qualifier("fourTRedisTemplate") private RedisTemplate fourTRedisTemplate; /** * 保存平台样式到 Redis */ @PreAuthorize("@ss.hasPermi('system:routes:edit')") @PostMapping("/savePlatformStyle") public AjaxResult savePlatformStyle(@RequestBody PlatformStyleDTO dto) { if (dto.getRoomId() == null || dto.getRouteId() == null || dto.getPlatformId() == null) { return AjaxResult.error("参数不完整"); } String key = "room:" + dto.getRoomId() + ":route:" + dto.getRouteId() + ":platforms"; redisTemplate.opsForHash().put(key, String.valueOf(dto.getPlatformId()), JSON.toJSONString(dto)); return success(); } /** * 从 Redis 获取平台样式 */ @PreAuthorize("@ss.hasPermi('system:routes:query')") @GetMapping("/getPlatformStyle") public AjaxResult getPlatformStyle(PlatformStyleDTO dto) { if (dto.getRoomId() == null || dto.getRouteId() == null || dto.getPlatformId() == null) { return AjaxResult.error("参数不完整"); } String key = "room:" + dto.getRoomId() + ":route:" + dto.getRouteId() + ":platforms"; Object val = redisTemplate.opsForHash().get(key, String.valueOf(dto.getPlatformId())); if (val != null) { return success(JSON.parseObject(val.toString(), PlatformStyleDTO.class)); } return success(); } /** * 保存4T数据到 Redis(按房间存储) */ @PreAuthorize("@ss.hasPermi('system:routes:edit')") @PostMapping("/save4TData") public AjaxResult save4TData(@RequestBody java.util.Map params) { Object roomId = params.get("roomId"); Object data = params.get("data"); if (roomId == null || data == null) { return AjaxResult.error("参数不完整"); } String key = "room:" + String.valueOf(roomId) + ":4t"; fourTRedisTemplate.opsForValue().set(key, data.toString()); return success(); } /** * 从 Redis 获取4T数据 */ @PreAuthorize("@ss.hasPermi('system:routes:query')") @GetMapping("/get4TData") public AjaxResult get4TData(Long roomId) { if (roomId == null) { return AjaxResult.error("房间ID不能为空"); } String key = "room:" + String.valueOf(roomId) + ":4t"; String val = fourTRedisTemplate.opsForValue().get(key); if (val != null && !val.isEmpty()) { try { return success(JSON.parseObject(val)); } catch (Exception e) { return success(val); } } return success(); } /** * 保存六步法任务页数据到 Redis(背景、图标、文本框) */ @PreAuthorize("@ss.hasPermi('system:routes:edit')") @PostMapping("/saveTaskPageData") public AjaxResult saveTaskPageData(@RequestBody java.util.Map params) { Object roomId = params.get("roomId"); Object data = params.get("data"); if (roomId == null || data == null) { return AjaxResult.error("参数不完整"); } String key = "room:" + String.valueOf(roomId) + ":task_page"; fourTRedisTemplate.opsForValue().set(key, data.toString()); return success(); } /** * 从 Redis 获取六步法任务页数据 */ @PreAuthorize("@ss.hasPermi('system:routes:query')") @GetMapping("/getTaskPageData") public AjaxResult getTaskPageData(Long roomId) { if (roomId == null) { return AjaxResult.error("房间ID不能为空"); } String key = "room:" + String.valueOf(roomId) + ":task_page"; String val = fourTRedisTemplate.opsForValue().get(key); if (val != null && !val.isEmpty()) { try { return success(JSON.parseObject(val)); } catch (Exception e) { return success(val); } } return success(); } /** * 保存六步法全部数据到 Redis(任务页、理解、后五步、背景、多页等) */ @PreAuthorize("@ss.hasPermi('system:routes:edit')") @PostMapping("/saveSixStepsData") public AjaxResult saveSixStepsData(@RequestBody java.util.Map params) { Object roomId = params.get("roomId"); Object data = params.get("data"); if (roomId == null || data == null) { return AjaxResult.error("参数不完整"); } String key = "room:" + String.valueOf(roomId) + ":six_steps"; fourTRedisTemplate.opsForValue().set(key, data.toString()); return success(); } /** * 从 Redis 获取六步法全部数据 */ @PreAuthorize("@ss.hasPermi('system:routes:query')") @GetMapping("/getSixStepsData") public AjaxResult getSixStepsData(Long roomId) { if (roomId == null) { return AjaxResult.error("房间ID不能为空"); } String key = "room:" + String.valueOf(roomId) + ":six_steps"; String val = fourTRedisTemplate.opsForValue().get(key); if (val != null && !val.isEmpty()) { try { return success(JSON.parseObject(val)); } catch (Exception e) { return success(val); } } return success(); } /** * 获取导弹发射参数列表(Redis,房间+航线+平台为 key,值为数组,每项含 angle/distance/launchTimeMinutesFromK/startLng/startLat/platformHeadingDeg) */ @PreAuthorize("@ss.hasPermi('system:routes:query')") @GetMapping("/missile-params") public AjaxResult getMissileParams(Long roomId, Long routeId, Long platformId) { if (roomId == null || routeId == null || platformId == null) { return AjaxResult.error("参数不完整"); } String key = "missile:params:" + roomId + ":" + routeId + ":" + platformId; Object val = redisTemplate.opsForValue().get(key); if (val == null) { return success(new ArrayList<>()); } String raw = val.toString(); if (raw.startsWith("[")) { return success(JSON.parseArray(raw)); } JSONObject single = JSON.parseObject(raw); List list = new ArrayList<>(); list.add(single); return success(list); } /** * 保存导弹发射参数到 Redis(追加一条记录,支持多次发射;每条含 angle/distance/launchTimeMinutesFromK/startLng/startLat/platformHeadingDeg) */ @PreAuthorize("@ss.hasPermi('system:routes:edit')") @PostMapping("/missile-params") public AjaxResult saveMissileParams(@RequestBody java.util.Map body) { Object roomId = body.get("roomId"); Object routeId = body.get("routeId"); Object platformId = body.get("platformId"); if (roomId == null || routeId == null || platformId == null) { return AjaxResult.error("参数不完整"); } String key = "missile:params:" + roomId + ":" + routeId + ":" + platformId; Object val = redisTemplate.opsForValue().get(key); JSONArray list = new JSONArray(); if (val != null) { String raw = val.toString(); if (raw.startsWith("[")) { list = JSON.parseArray(raw); } else { list.add(JSON.parseObject(raw)); } } list.add(body); redisTemplate.opsForValue().set(key, list.toJSONString()); return success(); } /** * 删除指定导弹发射参数(按数组索引删除,避免按 launchTimeMinutesFromK 匹配时浮点误差导致删错) */ @PreAuthorize("@ss.hasPermi('system:routes:edit')") @DeleteMapping("/missile-params") public AjaxResult deleteMissileParams( @RequestParam Long roomId, @RequestParam Long routeId, @RequestParam Long platformId, @RequestParam Integer index) { if (roomId == null || routeId == null || platformId == null || index == null) { return AjaxResult.error("参数不完整"); } String key = "missile:params:" + roomId + ":" + routeId + ":" + platformId; Object val = redisTemplate.opsForValue().get(key); if (val == null) { return success(); } String raw = val.toString(); JSONArray list; if (raw.startsWith("[")) { list = JSON.parseArray(raw); } else { list = new JSONArray(); list.add(JSON.parseObject(raw)); } int idx = index.intValue(); if (idx < 0 || idx >= list.size()) { return AjaxResult.error("索引越界"); } JSONArray filtered = new JSONArray(); for (int i = 0; i < list.size(); i++) { if (i != idx) { filtered.add(list.get(i)); } } if (filtered.isEmpty()) { redisTemplate.delete(key); } else { redisTemplate.opsForValue().set(key, filtered.toJSONString()); } return success(); } /** * 查询实体部署与航线列表 */ @PreAuthorize("@ss.hasPermi('system:routes:list')") @GetMapping("/list") public TableDataInfo list(Routes routes) { startPage(); List list = routesService.selectRoutesList(routes); return getDataTable(list); } /** * 导出实体部署与航线列表 */ @PreAuthorize("@ss.hasPermi('system:routes:export')") @Log(title = "实体部署与航线", businessType = BusinessType.EXPORT) @PostMapping("/export") public void export(HttpServletResponse response, Routes routes) { List list = routesService.selectRoutesList(routes); ExcelUtil util = new ExcelUtil(Routes.class); util.exportExcel(response, list, "实体部署与航线数据"); } /** * 获取实体部署与航线详细信息 */ @PreAuthorize("@ss.hasPermi('system:routes:query')") @GetMapping(value = "/{id}") public AjaxResult getInfo(@PathVariable("id") Long id) { return success(routesService.selectRoutesById(id)); } /** * 新增实体部署与航线 * @param roomId 可选,房间ID时记录到对象级操作日志便于按房间分页与回滚 */ @PreAuthorize("@ss.hasPermi('system:routes:add')") @Log(title = "实体部署与航线", businessType = BusinessType.INSERT) @PostMapping public AjaxResult add(@RequestBody Routes routes, @RequestParam(required = false) Long roomId) { // 1. 执行插入,MyBatis 会通过 useGeneratedKeys="true" 自动将新 ID 注入 routes 对象 int rows = routesService.insertRoutes(routes); if (rows > 0) { try { ObjectOperationLog opLog = new ObjectOperationLog(); opLog.setRoomId(roomId); opLog.setOperatorId(getUserId()); opLog.setOperatorName(getUsername()); opLog.setOperationType(ObjectOperationLog.TYPE_INSERT); opLog.setObjectType(ObjectOperationLog.OBJ_ROUTE); opLog.setObjectId(String.valueOf(routes.getId())); opLog.setObjectName(routes.getCallSign() != null ? routes.getCallSign() : "航线"); opLog.setDetail("新增航线:" + (routes.getCallSign() != null ? routes.getCallSign() : routes.getId())); opLog.setSnapshotAfter(JSON.toJSONString(routes)); objectOperationLogService.saveLog(opLog); } catch (Exception e) { logger.warn("记录操作日志失败", e); } } return rows > 0 ? AjaxResult.success(routes) : AjaxResult.error("新增航线失败"); } /** * 修改实体部署与航线 * @param roomId 可选,房间ID时记录到对象级操作日志 */ @PreAuthorize("@ss.hasPermi('system:routes:edit')") @Log(title = "实体部署与航线", businessType = BusinessType.UPDATE) @PutMapping public AjaxResult edit(@RequestBody Routes routes, @RequestParam(required = false) Long roomId) { Routes before = null; if (routes.getId() != null) { before = routesService.selectRoutesById(routes.getId()); } int rows = routesService.updateRoutes(routes); if (rows > 0 && before != null) { try { // 操作后快照用“更新后重新查库”的完整数据,避免请求体缺字段导致日志里误显示为“已清空” Routes after = routesService.selectRoutesById(routes.getId()); ObjectOperationLog opLog = new ObjectOperationLog(); opLog.setRoomId(roomId); opLog.setOperatorId(getUserId()); opLog.setOperatorName(getUsername()); opLog.setOperationType(ObjectOperationLog.TYPE_UPDATE); opLog.setObjectType(ObjectOperationLog.OBJ_ROUTE); opLog.setObjectId(String.valueOf(routes.getId())); opLog.setObjectName(routes.getCallSign() != null ? routes.getCallSign() : "航线"); opLog.setDetail("修改航线:" + (routes.getCallSign() != null ? routes.getCallSign() : routes.getId())); opLog.setSnapshotBefore(JSON.toJSONString(before)); opLog.setSnapshotAfter(after != null ? JSON.toJSONString(after) : JSON.toJSONString(routes)); objectOperationLogService.saveLog(opLog); } catch (Exception e) { logger.warn("记录操作日志失败", e); } } return toAjax(rows); } /** * 删除实体部署与航线(同时清除该航线在所有房间下的 Redis 导弹数据) * @param roomId 可选,房间ID时记录到对象级操作日志 */ @PreAuthorize("@ss.hasPermi('system:routes:remove')") @Log(title = "实体部署与航线", businessType = BusinessType.DELETE) @DeleteMapping("/{ids}") public AjaxResult remove(@PathVariable Long[] ids, @RequestParam(required = false) Long roomId) { for (Long id : ids) { Routes before = routesService.selectRoutesById(id); if (before != null) { try { ObjectOperationLog opLog = new ObjectOperationLog(); opLog.setRoomId(roomId); opLog.setOperatorId(getUserId()); opLog.setOperatorName(getUsername()); opLog.setOperationType(ObjectOperationLog.TYPE_DELETE); opLog.setObjectType(ObjectOperationLog.OBJ_ROUTE); opLog.setObjectId(String.valueOf(id)); opLog.setObjectName(before.getCallSign() != null ? before.getCallSign() : "航线"); opLog.setDetail("删除航线:" + (before.getCallSign() != null ? before.getCallSign() : id)); opLog.setSnapshotBefore(JSON.toJSONString(before)); objectOperationLogService.saveLog(opLog); } catch (Exception e) { logger.warn("记录操作日志失败", e); } } } int rows = routesService.deleteRoutesByIds(ids); if (rows > 0) { for (Long routeId : ids) { Set keys = redisTemplate.keys("missile:params:*:" + routeId + ":*"); if (keys != null && !keys.isEmpty()) { redisTemplate.delete(keys); } } } return toAjax(rows); } /** * 批量更新导弹发射位置(航线编辑后,根据新航点重算平台位置并更新 Redis 中的 startLng/startLat/platformHeadingDeg) * body: { roomId, routeId, platformId, updates: [{ launchTimeMinutesFromK, startLng, startLat, platformHeadingDeg }, ...] } */ @PreAuthorize("@ss.hasPermi('system:routes:edit')") @PutMapping("/missile-params/positions") public AjaxResult updateMissilePositions(@RequestBody java.util.Map body) { Object roomId = body.get("roomId"); Object routeId = body.get("routeId"); Object platformId = body.get("platformId"); Object updatesObj = body.get("updates"); if (roomId == null || routeId == null || platformId == null || updatesObj == null) { return AjaxResult.error("参数不完整"); } if (!(updatesObj instanceof java.util.List)) { return AjaxResult.error("updates 必须为数组"); } java.util.List updates = (java.util.List) updatesObj; if (updates.isEmpty()) return success(); String key = "missile:params:" + roomId + ":" + routeId + ":" + platformId; Object val = redisTemplate.opsForValue().get(key); if (val == null) return success(); String raw = val.toString(); JSONArray list; if (raw.startsWith("[")) { list = JSON.parseArray(raw); } else { list = new JSONArray(); list.add(JSON.parseObject(raw)); } for (int i = 0; i < list.size(); i++) { JSONObject item = list.getJSONObject(i); Double k = item.getDouble("launchTimeMinutesFromK"); if (k == null) continue; for (Object u : updates) { if (!(u instanceof java.util.Map)) continue; java.util.Map uMap = (java.util.Map) u; Object uk = uMap.get("launchTimeMinutesFromK"); if (uk == null) continue; double ukVal = uk instanceof Number ? ((Number) uk).doubleValue() : Double.parseDouble(uk.toString()); if (Math.abs(k - ukVal) < 1e-6) { if (uMap.containsKey("startLng")) item.put("startLng", uMap.get("startLng")); if (uMap.containsKey("startLat")) item.put("startLat", uMap.get("startLat")); if (uMap.containsKey("platformHeadingDeg")) item.put("platformHeadingDeg", uMap.get("platformHeadingDeg")); break; } } } redisTemplate.opsForValue().set(key, list.toJSONString()); return success(); } }