diff --git a/fys-admin/pom.xml b/fys-admin/pom.xml index ff80564f..bb8fe450 100644 --- a/fys-admin/pom.xml +++ b/fys-admin/pom.xml @@ -133,6 +133,19 @@ 1.5.7 compile + + + ws.schild + jave-core + 3.3.1 + + + + diff --git a/fys-admin/src/main/java/com/fuyuanshen/SimpleQR.java b/fys-admin/src/main/java/com/fuyuanshen/SimpleQR.java new file mode 100644 index 00000000..7f0b4b51 --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/SimpleQR.java @@ -0,0 +1,81 @@ +package com.fuyuanshen; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.File; + +public class SimpleQR { + public static void main(String[] args) throws Exception { + String text = "HELLO"; + int size = 21; // 版本1的大小 + boolean[][] qr = new boolean[size][size]; + + // 1. 三个定位标记 + for (int i = 0; i < 7; i++) + for (int j = 0; j < 7; j++) { + boolean black = i == 0 || i == 6 || j == 0 || j == 6 || (i > 1 && i < 5 && j > 1 && j < 5); + qr[i][j] = qr[i][size - 1 - j] = qr[size - 1 - i][j] = black; + } + + // 2. 定时图案(简化) + for (int i = 8; i < 13; i++) qr[i][6] = qr[6][i] = (i % 2 == 0); + + // 3. 编码数据(极简编码) + byte[] data = text.getBytes(); + int x = 20, y = 20, up = 1; // 从右下角开始 + for (byte b : data) + for (int bit = 7; bit >= 0; bit--) { + while (qr[x][y] || (x == 6 && y < 9) || (y == 6 && x < 9) || (x < 9 && y < 9) || + (x < 9 && y > size - 10) || (y < 9 && x > size - 10)) { + if (up > 0) { + if (--y < 0) { + y = 0; + x -= 2; + up = -1; + } + } else { + if (++y >= size) { + y = size - 1; + x -= 2; + up = 1; + } + } + if (x == 6) x--; + } + qr[x][y] = ((b >> bit) & 1) == 1; + if (up > 0) { + if (--y < 0) { + y = 0; + x -= 2; + up = -1; + } + } else { + if (++y >= size) { + y = size - 1; + x -= 2; + up = 1; + } + } + if (x == 6) x--; + } + + // 4. 保存图像 + int scale = 10, margin = 4; + BufferedImage img = new BufferedImage( + size * scale + margin * 2, size * scale + margin * 2, BufferedImage.TYPE_INT_RGB); + for (int i = 0; i < img.getWidth(); i++) + for (int j = 0; j < img.getHeight(); j++) + img.setRGB(i, j, 0xFFFFFF); // 白色背景 + + for (int i = 0; i < size; i++) + for (int j = 0; j < size; j++) + if (qr[i][j]) + for (int di = 0; di < scale; di++) + for (int dj = 0; dj < scale; dj++) + img.setRGB(margin + i * scale + di, margin + j * scale + dj, 0x000000); + + ImageIO.write(img, "PNG", new File("qr.png")); + System.out.println("二维码已生成"); + } + +} \ No newline at end of file diff --git a/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppFileController.java b/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppFileController.java index a2de6185..fd072249 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppFileController.java +++ b/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppFileController.java @@ -1,8 +1,8 @@ package com.fuyuanshen.app.controller; -import com.fuyuanshen.app.domain.bo.AppBusinessFileBo; +import com.fuyuanshen.equipment.domain.bo.AppBusinessFileBo; import com.fuyuanshen.app.domain.dto.AppFileDto; -import com.fuyuanshen.app.domain.vo.AppFileVo; +import com.fuyuanshen.equipment.domain.vo.AppFileVo; import com.fuyuanshen.app.service.AppFileService; import com.fuyuanshen.common.core.domain.R; import com.fuyuanshen.common.web.core.BaseController; diff --git a/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppOperationVideoController.java b/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppOperationVideoController.java index 5338ba2c..bcba5c87 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppOperationVideoController.java +++ b/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppOperationVideoController.java @@ -1,8 +1,8 @@ package com.fuyuanshen.app.controller; -import com.fuyuanshen.app.domain.bo.AppOperationVideoBo; -import com.fuyuanshen.app.domain.vo.AppOperationVideoVo; -import com.fuyuanshen.app.service.IAppOperationVideoService; +import com.fuyuanshen.equipment.domain.bo.AppOperationVideoBo; +import com.fuyuanshen.equipment.domain.vo.AppOperationVideoVo; +import com.fuyuanshen.equipment.service.IAppOperationVideoService; import com.fuyuanshen.common.core.domain.R; import com.fuyuanshen.common.core.domain.model.AppLoginUser; import com.fuyuanshen.common.satoken.utils.AppLoginHelper; diff --git a/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppVideoController.java b/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppVideoController.java index 63ddfb37..2c186f67 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppVideoController.java +++ b/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppVideoController.java @@ -1,13 +1,20 @@ package com.fuyuanshen.app.controller; import cn.dev33.satoken.annotation.SaIgnore; +import com.fuyuanshen.app.domain.dto.AppAudioFileDto; +import com.fuyuanshen.app.domain.dto.AppFileDto; +import com.fuyuanshen.app.domain.dto.AppFileRenameDto; +import com.fuyuanshen.app.domain.dto.TextToSpeechRequest; import com.fuyuanshen.app.service.AudioProcessService; import com.fuyuanshen.app.service.VideoProcessService; import com.fuyuanshen.common.core.domain.R; +import com.fuyuanshen.common.core.utils.StringUtils; import com.fuyuanshen.common.idempotent.annotation.RepeatSubmit; import com.fuyuanshen.common.web.core.BaseController; +import com.fuyuanshen.equipment.domain.vo.AppFileVo; import lombok.RequiredArgsConstructor; import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; @@ -56,6 +63,7 @@ public class AppVideoController extends BaseController { return R.ok(audioProcessService.generateStandardPcmData(text)); } + /** * 提取文本内容(只支持txt/docx) */ @@ -65,4 +73,73 @@ public class AppVideoController extends BaseController { return R.ok("Success",audioProcessService.extract(file)); } + + /** + * 上传音频文件进行处理 + * 支持MP3、WAV、PCM格式 + */ + @PostMapping("/uploadAudioToOss") + public R uploadAudioToOss(@ModelAttribute AppAudioFileDto bo) { + try { + bo.setSource("app"); + String result = audioProcessService.uploadAudioToOss(bo); + return R.ok(result); + } catch (IllegalArgumentException e) { + return R.fail("文件格式错误: " + e.getMessage()); + } catch (Exception e) { + + return R.fail("上传处理失败: " + e.getMessage()); + } + } + + /** + * 文本转语音 + * 支持MP3、WAV、PCM格式 + */ + @PostMapping("/ttsToOss") + public R textToSpeech(@RequestBody TextToSpeechRequest request) { + try { + if (request.getDeviceId() == null) { + return R.fail("设备ID不能为空"); + } + if (StringUtils.isBlank(request.getText())) { + return R.fail("文本不能为空"); + } + + String result = audioProcessService.textToSpeech( + request.getDeviceId(), + request.getText(), + request.getFileSuffix(), + "app" + ); + return R.ok(result); + } catch (Exception e) { + return R.fail("文本转语音失败: " + e.getMessage()); + } + } + + + /** + * 查询语音文件列表 + */ + @GetMapping("/queryAudioFileList") + public R> queryAudioFileList(Long deviceId) { + return R.ok(audioProcessService.queryAudioFileList(deviceId,"app")); + } + + /** + * 删除语音文件 + */ + @GetMapping("/deleteAudioFile") + public R deleteAudioFile(Long id) { + return audioProcessService.deleteAudioFile(id); + } + + /** + * 文件重命名 + */ + @PostMapping("/renameAudioFile") + public R renameAudioFile(@RequestBody AppFileRenameDto bo) { + return audioProcessService.renameAudioFile(bo); + } } diff --git a/fys-admin/src/main/java/com/fuyuanshen/app/controller/device/AppDeviceXinghanController.java b/fys-admin/src/main/java/com/fuyuanshen/app/controller/device/AppDeviceXinghanController.java index 47f552a5..6746d867 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/app/controller/device/AppDeviceXinghanController.java +++ b/fys-admin/src/main/java/com/fuyuanshen/app/controller/device/AppDeviceXinghanController.java @@ -26,6 +26,7 @@ import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import java.util.List; +import java.util.Map; /** * HBY670设备控制类 @@ -133,7 +134,7 @@ public class AppDeviceXinghanController extends BaseController { } // @Log("新增设备") - @Operation(summary = "新增设备") + @Log(title = "新增设备") @PostMapping(value = "/add") public R addDevice(@RequestBody DeviceForm deviceForm) { try { @@ -144,4 +145,26 @@ public class AppDeviceXinghanController extends BaseController { return R.ok(); } + @PostMapping(value = "/GetDeviceByName") + @Operation(summary = "通过蓝牙名/设备名称查询设备") + public R GetDeviceByName(@RequestBody DeviceForm deviceForm) { + Object device = appDeviceService.GetDeviceByName(deviceForm); + return R.ok(device); + } + + + @PostMapping(value = "/getEquipCountByType") + @Operation(summary = "查询某个类型下的设备总数量") + public R getEquipCountByType(@RequestBody DeviceForm deviceForm) { + Object device = appDeviceService.getEquipCountByType(deviceForm); + return R.ok(device); + } + + @PostMapping(value = "/getEquipAllByType") + @Operation(summary = "查询某个类型下的设备") + public R>> getEquipAllByType(@RequestBody DeviceForm deviceForm){ + List> list=appDeviceService.getEquipAllByType(deviceForm); + return R.ok(list); + } + } diff --git a/fys-admin/src/main/java/com/fuyuanshen/app/controller/device/bjq/AppDeviceHBY100JController.java b/fys-admin/src/main/java/com/fuyuanshen/app/controller/device/bjq/AppDeviceHBY100JController.java new file mode 100644 index 00000000..193abd9a --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/app/controller/device/bjq/AppDeviceHBY100JController.java @@ -0,0 +1,104 @@ +package com.fuyuanshen.app.controller.device.bjq; + +import com.fuyuanshen.app.domain.vo.AppDeviceHBY100JDetailVo; +import com.fuyuanshen.common.core.domain.R; +import com.fuyuanshen.common.web.core.BaseController; +import com.fuyuanshen.web.controller.device.domain.dto.*; +import com.fuyuanshen.web.service.device.DeviceHBY100JBizService; +import jakarta.validation.constraints.NotNull; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +/** + * HBY100J设备控制类 + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/app/hby100j/device") +public class AppDeviceHBY100JController extends BaseController { + + private final DeviceHBY100JBizService deviceHBY100JBizService; + + + /** + * 获取设备详细信息 + * + * @param id 主键 + */ + @GetMapping("/{id}") + public R getInfo(@NotNull(message = "主键不能为空") + @PathVariable Long id) { + return R.ok(deviceHBY100JBizService.getInfo(id)); + } + + /** + * 更新语音 + */ + @PostMapping("/updateVoice") + public R updateVoice(@RequestBody HBY100JUpdateVoiceDto dto) { + deviceHBY100JBizService.updateVoice(dto); + return R.ok(); + } + + /** + * 强制报警 + * + */ + + @PostMapping("/forceAlarmActivation") + public R forceAlarmActivation(@RequestBody HBY100JForceAlarmActivationDto bo) { + deviceHBY100JBizService.forceAlarmActivation(bo); + return R.ok(); + } + + /** + * 语音播报 + * + */ + @PostMapping("/voiceBroadcast") + public R voiceBroadcast(@RequestBody HBY100JVoiceBroadcastDto params) { + deviceHBY100JBizService.voiceBroadcast(params); + return R.ok(); + } + + + /** + * 爆闪模式 + */ + @PostMapping("/strobeMode") + public R strobeMode(@RequestBody HBY100JStrobeModeDto params) { + deviceHBY100JBizService.strobeMode(params); + return R.ok(); + } + + + /** + * 灯光调节 + */ + @PostMapping("/lightAdjustment") + public R lightAdjustment(@RequestBody HBY100JLightAdjustmentDto params) { + deviceHBY100JBizService.lightAdjustment(params); + return R.ok(); + } + + + /** + * 爆闪频率 + */ + @PostMapping("/strobeFrequency") + public R strobeFrequency(@RequestBody HBY100JStrobeFrequencyDto params) { + deviceHBY100JBizService.strobeFrequency(params); + return R.ok(); + } + + /** + * 修改音量 + */ + @PostMapping("/updateVolume") + public R updateVolume(@RequestBody HBY100JUpdateVolumeDto params) { + deviceHBY100JBizService.updateVolume(params); + return R.ok(); + } +} diff --git a/fys-admin/src/main/java/com/fuyuanshen/app/domain/dto/AppAudioFileDto.java b/fys-admin/src/main/java/com/fuyuanshen/app/domain/dto/AppAudioFileDto.java new file mode 100644 index 00000000..0d632cb6 --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/app/domain/dto/AppAudioFileDto.java @@ -0,0 +1,21 @@ +package com.fuyuanshen.app.domain.dto; + +import lombok.Data; +import org.springframework.web.multipart.MultipartFile; + +@Data +public class AppAudioFileDto { + + private Long deviceId; + + /** + * 文件 + */ + private MultipartFile file; + + + /** + * 数据来源 1:app 2:web 3:其他 + */ + private String source; +} diff --git a/fys-admin/src/main/java/com/fuyuanshen/app/domain/dto/AppFileRenameDto.java b/fys-admin/src/main/java/com/fuyuanshen/app/domain/dto/AppFileRenameDto.java new file mode 100644 index 00000000..a9621a57 --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/app/domain/dto/AppFileRenameDto.java @@ -0,0 +1,26 @@ +package com.fuyuanshen.app.domain.dto; + +import lombok.Data; +import org.springframework.web.multipart.MultipartFile; + +@Data +public class AppFileRenameDto { + + private Long id; + + /** + * 文件id + */ + private Long fileId; + + /** + * 设备id + */ + private Long deviceId; + + /** + * 文件名称 + */ + private String fileName; + +} diff --git a/fys-admin/src/main/java/com/fuyuanshen/app/domain/dto/PcmGenerationRequest.java b/fys-admin/src/main/java/com/fuyuanshen/app/domain/dto/PcmGenerationRequest.java new file mode 100644 index 00000000..bc58bda0 --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/app/domain/dto/PcmGenerationRequest.java @@ -0,0 +1,11 @@ +package com.fuyuanshen.app.domain.dto; + +import lombok.Data; + +/** + * PCM生成请求实体 + */ +@Data +public class PcmGenerationRequest { + private String text; +} \ No newline at end of file diff --git a/fys-admin/src/main/java/com/fuyuanshen/app/domain/dto/TextToSpeechRequest.java b/fys-admin/src/main/java/com/fuyuanshen/app/domain/dto/TextToSpeechRequest.java new file mode 100644 index 00000000..6888e515 --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/app/domain/dto/TextToSpeechRequest.java @@ -0,0 +1,19 @@ +package com.fuyuanshen.app.domain.dto; + +import lombok.Data; + +/** + * 文本转语音请求实体 + */ +@Data +public class TextToSpeechRequest { + + private Long deviceId; + + private String text; + + /** + * 文件后缀格式 + */ + private String fileSuffix; +} \ No newline at end of file diff --git a/fys-admin/src/main/java/com/fuyuanshen/app/http/HttpTtsClient.java b/fys-admin/src/main/java/com/fuyuanshen/app/http/HttpTtsClient.java new file mode 100644 index 00000000..c76c0664 --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/app/http/HttpTtsClient.java @@ -0,0 +1,133 @@ +package com.fuyuanshen.app.http; + +import com.alibaba.nls.client.AccessToken; +import com.fuyuanshen.common.redis.utils.RedisUtils; + +import java.io.*; +import java.net.HttpURLConnection; +import java.net.URL; +import java.time.Duration; +import java.util.Base64; +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; + +import static cn.dev33.satoken.SaManager.log; + +public class HttpTtsClient { + + private String accessKeyId; + private String accessKeySecret; + private String appKey; + /** + * 阿里云TTS服务基础URL + */ + private static final String BASE_URL = "https://nls-gateway-cn-shanghai.aliyuncs.com/stream/v1/tts"; + + public HttpTtsClient(String accessKeyId, String accessKeySecret, String appKey) { + this.accessKeyId = accessKeyId; + this.accessKeySecret = accessKeySecret; + this.appKey = appKey; + } + private String refreshAccessToken() { + try { + // 调用阿里云API获取访问令牌 + AccessToken accessToken = new AccessToken(accessKeyId, accessKeySecret); + accessToken.apply(); + String token = accessToken.getToken(); + log.info("访问令牌刷新成功"); + + return token; + } catch (Exception e) { + log.error("刷新访问令牌失败: {}", e.getMessage(), e); + return null; + } + } + /** + * 使用HTTP POST方式调用阿里云TTS服务生成MP3格式语音 + */ + public byte[] synthesizeTextToMp3(String text,String fileSuffix) throws IOException { + String endpoint = "https://nls-gateway-cn-shanghai.aliyuncs.com/stream/v1/tts"; + + // 构建请求体 +// String requestBody = String.format( +// "{\"appkey\":\"%s\",\"text\":\"%s\",\"voice\":\"zhifeng\",\"format\":\"MP3\",\"sample_rate\":24000,\"volume\":50,\"speech_rate\":0,\"pitch_rate\":0}", +// appKey, +// text.replace("\"", "\\\"")zhide +// ); + + String token = refreshAccessToken(); + String requestBody = " {\n" + + " \"appkey\":\""+appKey+"\",\n" + + " \"voice\":\"zhide\",\n" + + " \"text\":\""+text+"\",\n" + + " \"token\":\""+token+"\",\n" + + " \"volume\": 100,\n" + + " \"pitch_rate\": 0,\n" + + " \"format\":\""+fileSuffix+"\"\n" + + " }"; + + URL url = new URL(endpoint); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + + // 设置请求方法和头部 + conn.setRequestMethod("POST"); + conn.setRequestProperty("Content-Type", "application/json"); + conn.setRequestProperty("Authorization", buildAuthorization()); + conn.setRequestProperty("Content-Length", String.valueOf(requestBody.getBytes().length)); + conn.setDoOutput(true); + + // 发送请求体 + try (OutputStream os = conn.getOutputStream()) { + os.write(requestBody.getBytes("UTF-8")); + } + + // 读取响应 + int responseCode = conn.getResponseCode(); + if (responseCode == HttpURLConnection.HTTP_OK) { + // 读取音频数据 + try (InputStream is = conn.getInputStream()) { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + int nRead; + byte[] data = new byte[1024]; + while ((nRead = is.read(data, 0, data.length)) != -1) { + buffer.write(data, 0, nRead); + } + return buffer.toByteArray(); + } + } else { + throw new IOException("HTTP请求失败,状态码: " + responseCode); + } + } + + /** + * 构建授权头部 + */ + private String buildAuthorization() { + // 实际实现需要根据阿里云API规范构建签名 + // 这里是简化示例 + return "Bearer " + generateAccessToken(); + } + + /** + * 生成访问令牌 + */ + private String generateAccessToken() { + // 实际实现需要调用阿里云获取token的API + return "your-access-token"; + } + + /** + * 保存MP3到文件 + */ + public String saveMp3ToFile(String text, String outputPath) throws IOException { + byte[] mp3Data = synthesizeTextToMp3(text,"mp3"); + + try (FileOutputStream fos = new FileOutputStream(outputPath)) { + fos.write(mp3Data); + } + + return outputPath; + } +} diff --git a/fys-admin/src/main/java/com/fuyuanshen/app/http/HttpTtsExample.java b/fys-admin/src/main/java/com/fuyuanshen/app/http/HttpTtsExample.java new file mode 100644 index 00000000..974afb69 --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/app/http/HttpTtsExample.java @@ -0,0 +1,32 @@ +package com.fuyuanshen.app.http; + +import java.io.IOException; + +public class HttpTtsExample { + + /** + * appKey: lbGuq5K5bEH4uxmT + * akId: LTAI5t66moCkhNC32TDJ5ReP + * akSecret: 2F3sdoBJ08bYvJcuDgSkLnJwGXsvYH + * @param args + */ + public static void main(String[] args) { + String accessKeyId = "LTAI5t66moCkhNC32TDJ5ReP"; + String accessKeySecret = "2F3sdoBJ08bYvJcuDgSkLnJwGXsvYH"; + String appKey = "lbGuq5K5bEH4uxmT"; + + try { + // 使用HTTP方式调用 + HttpTtsClient httpClient = new HttpTtsClient(accessKeyId, accessKeySecret, appKey); + + String text = "前方拥堵,请绕道而行。"; + String outputPath = "D:\\http_output11.mp3"; + + String resultFile = httpClient.saveMp3ToFile(text, outputPath); + System.out.println("MP3音频已保存至: " + resultFile); + + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/fys-admin/src/main/java/com/fuyuanshen/app/http/OkHttpTtsClient.java b/fys-admin/src/main/java/com/fuyuanshen/app/http/OkHttpTtsClient.java new file mode 100644 index 00000000..eaf29392 --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/app/http/OkHttpTtsClient.java @@ -0,0 +1,58 @@ +package com.fuyuanshen.app.http; + +import okhttp3.*; + +import java.io.IOException; + +public class OkHttpTtsClient { + + private OkHttpClient client = new OkHttpClient(); + private String accessKeyId; + private String accessKeySecret; + private String appKey; + + public OkHttpTtsClient(String accessKeyId, String accessKeySecret, String appKey) { + this.accessKeyId = accessKeyId; + this.accessKeySecret = accessKeySecret; + this.appKey = appKey; + } + + /** + * 使用OkHttp调用TTS服务 + */ + public byte[] synthesizeTextToMp3(String text) throws IOException { + MediaType JSON = MediaType.parse("application/json; charset=utf-8"); + + String jsonPayload = String.format( + "{\"appkey\":\"%s\",\"text\":\"%s\",\"voice\":\"zhifeng\",\"format\":\"MP3\",\"sample_rate\":24000,\"volume\":50,\"speech_rate\":0,\"pitch_rate\":0}", + appKey, + text.replace("\"", "\\\"") + ); + + RequestBody body = RequestBody.create(JSON,jsonPayload); + Request request = new Request.Builder() + .url("https://nls-gateway.cn-shanghai.aliyuncs.com/v1/tts") + .post(body) + .addHeader("Content-Type", "application/json") + .addHeader("Authorization", "Bearer " + getAccessToken()) + .build(); + + try (Response response = client.newCall(request).execute()) { + if (!response.isSuccessful()) { + throw new IOException("HTTP请求失败: " + response.code()); + } + + ResponseBody responseBody = response.body(); + if (responseBody != null) { + return responseBody.bytes(); + } else { + throw new IOException("响应体为空"); + } + } + } + + private String getAccessToken() { + // 实现获取访问令牌的逻辑 + return "your-access-token"; + } +} diff --git a/fys-admin/src/main/java/com/fuyuanshen/app/http/TokenClient.java b/fys-admin/src/main/java/com/fuyuanshen/app/http/TokenClient.java new file mode 100644 index 00000000..40c2984d --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/app/http/TokenClient.java @@ -0,0 +1,48 @@ +package com.fuyuanshen.app.http; + +import java.io.*; +import java.net.HttpURLConnection; +import java.net.URL; + +/** + * 获取阿里云TTS服务访问令牌 + */ +public class TokenClient { + + public static String getAccessToken(String accessKeyId, String accessKeySecret) throws IOException { + String endpoint = "https://nls-meta.cn-shanghai.aliyuncs.com/v1/token"; + + URL url = new URL(endpoint); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + + conn.setRequestMethod("POST"); + conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); + conn.setDoOutput(true); + + // 发送认证信息 + String params = String.format("grant_type=client_credentials&client_id=%s&client_secret=%s", + accessKeyId, accessKeySecret); + + try (OutputStream os = conn.getOutputStream()) { + os.write(params.getBytes("UTF-8")); + } + + int responseCode = conn.getResponseCode(); + if (responseCode == HttpURLConnection.HTTP_OK) { + try (InputStream is = conn.getInputStream(); + BufferedReader reader = new BufferedReader(new InputStreamReader(is))) { + + StringBuilder response = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + response.append(line); + } + + // 解析响应获取token(实际需要使用JSON解析库) + return response.toString().replaceAll(".*\"access_token\":\"([^\"]+)\".*", "$1"); + } + } else { + throw new IOException("获取令牌失败,状态码: " + responseCode); + } + } +} diff --git a/fys-admin/src/main/java/com/fuyuanshen/app/service/AppFileService.java b/fys-admin/src/main/java/com/fuyuanshen/app/service/AppFileService.java index 0b450f39..09c52758 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/app/service/AppFileService.java +++ b/fys-admin/src/main/java/com/fuyuanshen/app/service/AppFileService.java @@ -1,13 +1,14 @@ package com.fuyuanshen.app.service; -import com.fuyuanshen.app.domain.bo.AppBusinessFileBo; +import com.fuyuanshen.equipment.domain.bo.AppBusinessFileBo; import com.fuyuanshen.app.domain.dto.AppFileDto; -import com.fuyuanshen.app.domain.vo.AppBusinessFileVo; -import com.fuyuanshen.app.domain.vo.AppFileVo; +import com.fuyuanshen.equipment.domain.vo.AppBusinessFileVo; +import com.fuyuanshen.equipment.domain.vo.AppFileVo; import com.fuyuanshen.common.core.exception.ServiceException; import com.fuyuanshen.common.oss.core.OssClient; import com.fuyuanshen.common.oss.factory.OssFactory; import com.fuyuanshen.common.satoken.utils.AppLoginHelper; +import com.fuyuanshen.equipment.service.IAppBusinessFileService; import com.fuyuanshen.equipment.utils.FileHashUtil; import com.fuyuanshen.system.domain.vo.SysOssVo; import com.fuyuanshen.system.service.ISysOssService; @@ -96,5 +97,4 @@ public class AppFileService { } return appBusinessFileService.deleteWithValidByIds(List.of(ids), true); } - } diff --git a/fys-admin/src/main/java/com/fuyuanshen/app/service/AudioProcessService.java b/fys-admin/src/main/java/com/fuyuanshen/app/service/AudioProcessService.java index 31c5ac36..9b0916c2 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/app/service/AudioProcessService.java +++ b/fys-admin/src/main/java/com/fuyuanshen/app/service/AudioProcessService.java @@ -1,9 +1,30 @@ package com.fuyuanshen.app.service; +import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; +import com.fuyuanshen.app.domain.bo.AppDeviceBindRecordBo; +import com.fuyuanshen.app.domain.dto.AppAudioFileDto; +import com.fuyuanshen.app.domain.dto.AppFileRenameDto; +import com.fuyuanshen.app.domain.vo.AppDeviceBindRecordVo; +import com.fuyuanshen.app.http.HttpTtsClient; +import com.fuyuanshen.common.core.domain.R; +import com.fuyuanshen.common.satoken.utils.AppLoginHelper; +import com.fuyuanshen.common.satoken.utils.LoginHelper; +import com.fuyuanshen.equipment.domain.AppBusinessFile; +import com.fuyuanshen.equipment.domain.bo.AppBusinessFileBo; +import com.fuyuanshen.equipment.domain.vo.AppFileVo; +import com.fuyuanshen.equipment.mapper.AppBusinessFileMapper; +import com.fuyuanshen.equipment.service.IAppBusinessFileService; import com.fuyuanshen.equipment.utils.AlibabaTTSUtil; import com.fuyuanshen.equipment.utils.AudioProcessUtil; +import com.fuyuanshen.equipment.utils.FileHashUtil; +import com.fuyuanshen.equipment.utils.Mp3Duration; +import com.fuyuanshen.global.mqtt.utils.FfmpegVolumeUtil; +import com.fuyuanshen.global.mqtt.utils.FfmpegVolumeUtil3; +import com.fuyuanshen.system.domain.vo.SysOssVo; +import com.fuyuanshen.system.service.ISysOssService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; @@ -16,9 +37,12 @@ import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; import java.io.*; import java.nio.charset.StandardCharsets; -import java.nio.file.Files; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import java.util.Arrays; import java.util.List; +import java.util.Random; +import java.util.UUID; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; @@ -39,6 +63,22 @@ public class AudioProcessService { private final AudioProcessUtil audioProcessUtil; private final AlibabaTTSUtil alibabaTTSUtil; + private final FileHashUtil fileHashUtil; + private final ISysOssService ossService; + private final IAppBusinessFileService appBusinessFileService; + private final AppBusinessFileMapper appBusinessFileMapper; + private final IAppDeviceBindRecordService appDeviceBindRecordService; + +// String accessKeyId = "LTAI5t66moCkhNC32TDJ5ReP"; +// String accessKeySecret = "2F3sdoBJ08bYvJcuDgSkLnJwGXsvYH"; +// String appKey = "lbGuq5K5bEH4uxmT"; + @Value("${alibaba.tts.appKey}") + private String appKey; + @Value("${alibaba.tts.akId}") + private String accessKeyId; + @Value("${alibaba.tts.akSecret}") + private String accessKeySecret; + /** * 处理上传的音频文件 */ @@ -158,7 +198,7 @@ public class AudioProcessService { private String saveByteArrayToFile(byte[] data, String filename) throws IOException { // 确定保存路径(可以是临时目录或指定目录) - String directory = System.getProperty("java.io.tmpdir"); // 使用系统临时目录 + String directory = System.getProperty("java.io.tmpdir");// 使用系统临时目录 File dir = new File(directory); if (!dir.exists()) { dir.mkdirs(); @@ -172,9 +212,32 @@ public class AudioProcessService { fos.write(data); } + return file.getAbsolutePath(); } + private String saveByteArrayToFile(InputStream inputStream, String filename) throws IOException { + // 确定保存路径(可以是临时目录或指定目录) + String directory = System.getProperty("java.io.tmpdir"); // 使用系统临时目录 + File dir = new File(directory); + if (!dir.exists()) { + dir.mkdirs(); + } + + // 创建完整文件路径 + File file = new File(dir, filename); + + // 从输入流读取数据并写入文件 + try (FileOutputStream fos = new FileOutputStream(file); + InputStream is = inputStream) { + byte[] buffer = new byte[8192]; // 8KB缓冲区 + int bytesRead; + while ((bytesRead = is.read(buffer)) != -1) { + fos.write(buffer, 0, bytesRead); + } + } + return file.getAbsolutePath(); + } /** @@ -305,4 +368,234 @@ public class AudioProcessService { } + public String uploadAudioToOss(AppAudioFileDto bo) { + MultipartFile file = bo.getFile(); + // 校验文件格式和大小 + validateAudioFileForRestful(file); + // 上传文件 + // SysOssVo upload = sysOssService.upload(file); + String filename = file.getOriginalFilename(); + String savedPath = null; + try { + String fileHash = fileHashUtil.hash(file); + SysOssVo upload = ossService.updateHash(file, fileHash); + // 强制将HTTP替换为HTTPS + if (upload.getUrl() != null && upload.getUrl().startsWith("http://")) { + upload.setUrl(upload.getUrl().replaceFirst("^http://", "https://")); + } + String fileSuffix = filename.substring(filename.lastIndexOf('.')).toLowerCase(); + AppBusinessFileBo appBusinessFileBo = new AppBusinessFileBo(); + appBusinessFileBo.setFileId(upload.getOssId()); + appBusinessFileBo.setBusinessId(bo.getDeviceId()); + appBusinessFileBo.setFileType(3L); + Long userId = getUserId(bo.getSource(), bo.getDeviceId()); + appBusinessFileBo.setCreateBy(userId); + savedPath = saveByteArrayToFile(file.getInputStream(), generateRandomFileName(fileSuffix)); + if (savedPath != null) { + log.info("MP3文件已保存: {}", savedPath); + Integer mp3Duration = Mp3Duration.getMp3Duration(savedPath); + log.info("MP3文件时长: {} 秒", mp3Duration); + appBusinessFileBo.setDuration(mp3Duration); + } + appBusinessFileService.insertByBo(appBusinessFileBo); + if (upload != null) { + return upload.getUrl(); + } + } catch (Exception e){ + log.error("上传音频文件失败", e); + }finally { + log.info("删除临时文件: {}", savedPath); + if(savedPath != null){ + deleteTempFile(new File(savedPath)); + } + } + + return null; + } + + private Long getUserId(String source,Long deviceId){ + if("app".equals( source)){ + return AppLoginHelper.getUserId(); + } else if ("web".equals( source)){ +// AppDeviceBindRecordBo appDeviceBindRecordBo = new AppDeviceBindRecordBo(); +// appDeviceBindRecordBo.setDeviceId(deviceId); +//// appDeviceBindRecordBo.setCommunicationMode(0); +// List appDeviceBindRecordVos = appDeviceBindRecordService.queryList(appDeviceBindRecordBo); +// if (appDeviceBindRecordVos != null && !appDeviceBindRecordVos.isEmpty()){ +// userId = appDeviceBindRecordVos.get(0).getBindingUserId(); +// } + } + return 0L; + } + /** + * 校验音频文件格式 + */ + private void validateAudioFileForRestful(MultipartFile file) { + if (file == null || file.isEmpty()) { + throw new IllegalArgumentException("上传文件不能为空"); + } + + String originalFilename = file.getOriginalFilename(); + if (originalFilename == null) { + throw new IllegalArgumentException("文件名不能为空"); + } + List SUPPORTED_FORMATS = Arrays.asList( + ".wav", ".mp3", ".pcm" + ); + + String ext = originalFilename.substring(originalFilename.lastIndexOf('.')).toLowerCase(); + // 检查文件扩展名 + if (!SUPPORTED_FORMATS.contains(ext)) { + throw new IllegalArgumentException("只允许上传MP3、WAV、PCM格式的音频文件"); + } + + long fileSize = 256 * 1024; + // 检查文件大小 + if (file.getSize() > fileSize) { + throw new IllegalArgumentException("音频大小不能超过256kb"); + } + } + + /** + * 判断文件是否为支持的音频格式 + */ + private boolean isSupportedFormat(String filename) { + if (filename == null || filename.lastIndexOf('.') == -1) { + return false; + } + String ext = filename.substring(filename.lastIndexOf('.')).toLowerCase(); + return SUPPORTED_FORMATS.contains(ext); + } + + public String textToSpeech(Long deviceId,String text, String fileSuffix,String source) { + //支持PCM/WAV/MP3格式 + if (fileSuffix == null || fileSuffix.isEmpty()) { + fileSuffix = "mp3"; + } + fileSuffix = fileSuffix.toLowerCase(); + List SUPPORTED_FORMATS = Arrays.asList( + "wav", "mp3", "pcm" + ); + boolean contains = SUPPORTED_FORMATS.contains(fileSuffix); + if (!contains) { + throw new IllegalArgumentException("不支持的音频格式"); + } +// String accessKeyId = "LTAI5t66moCkhNC32TDJ5ReP"; +// String accessKeySecret = "2F3sdoBJ08bYvJcuDgSkLnJwGXsvYH"; +// String appKey = "lbGuq5K5bEH4uxmT"; + String savedPath = null; + String savedMp3VolumePath = null; + try { + // 使用HTTP方式调用 + HttpTtsClient httpClient = new HttpTtsClient(accessKeyId, accessKeySecret, appKey); +// + byte[] mp3Data = httpClient.synthesizeTextToMp3(text,fileSuffix); +// byte[] mp3Data = alibabaTTSUtil.synthesizeTextToMp3(text,fileSuffix); + + + AppBusinessFileBo appBusinessFileBo = new AppBusinessFileBo(); + + savedPath = saveByteArrayToFile(mp3Data, generateRandomFileName(fileSuffix)); + + Integer mp3Duration = getMp3Duration2(savedPath); + appBusinessFileBo.setDuration(mp3Duration); + + String directory = System.getProperty("java.io.tmpdir"); // 使用系统临时目录 + String fileName = generateRandomFileName(fileSuffix); + savedMp3VolumePath = directory + "/" + fileName; + log.info("保存MP3文件: {}", savedMp3VolumePath); + FfmpegVolumeUtil.increaseMp3Volume(savedPath, savedMp3VolumePath, 8); + + File file = new File(savedMp3VolumePath); + String fileHash = fileHashUtil.getFileHash(file,"SHA-256"); + SysOssVo upload = ossService.updateHash(file, fileHash); + + // 强制将HTTP替换为HTTPS + if (upload.getUrl() != null && upload.getUrl().startsWith("http://")) { + upload.setUrl(upload.getUrl().replaceFirst("^http://", "https://")); + } + + appBusinessFileBo.setFileId(upload.getOssId()); + appBusinessFileBo.setBusinessId(deviceId); + appBusinessFileBo.setFileType(3L); + Long userId = getUserId(source, deviceId); + appBusinessFileBo.setCreateBy(userId); + appBusinessFileService.insertByBo(appBusinessFileBo); + if (upload != null) { + return upload.getUrl(); + } + } catch (Exception e){ + log.error("上传音频文件失败", e); + } finally { + log.info("删除savedPath临时文件: {}", savedPath); + if(savedPath != null){ + deleteTempFile(new File(savedPath)); + } + if(savedMp3VolumePath != null){ + log.info("删除savedMp3VolumePath临时文件: {}", savedMp3VolumePath); + deleteTempFile(new File(savedMp3VolumePath)); + } + } + return null; + } + + private Integer getMp3Duration2(String savedPath) { + if (savedPath != null) { + log.info("MP3文件已保存: {}", savedPath); + Integer mp3Duration = Mp3Duration.getMp3Duration(savedPath); + log.info("MP3文件时长: {} 秒", mp3Duration); + return mp3Duration; + } + return 0; + } + + private static final Random random = new Random(); + private static final DateTimeFormatter formatter = + DateTimeFormatter.ofPattern("yyyyMMddHHmmss"); + /** + * 生成纯随机文件名(无原文件名依赖) + */ + public static String generateRandomFileName(String extension) { + String timestamp = LocalDateTime.now().format(formatter); + int randomNum = random.nextInt(10000); + String uuidPart = UUID.randomUUID().toString().substring(0, 8); + + if (!extension.startsWith(".")) { + extension = "." + extension; + } + + return timestamp+extension; + } + + public List queryAudioFileList(Long deviceId,String source) { + if(deviceId == null){ + return null; + } + + Long userId = getUserId(source, deviceId); + AppBusinessFileBo bo = new AppBusinessFileBo(); + bo.setBusinessId(deviceId); + bo.setFileType(3L); + if("web".equals(source)){ + bo.setCreateBy(null); + }else{ + bo.setCreateBy(userId); + } + return appBusinessFileService.queryAppFileList(bo); + } + + public R deleteAudioFile(Long id) { + UpdateWrapper updateWrapper = new UpdateWrapper<>(); + updateWrapper.eq("id",id); + appBusinessFileMapper.delete(updateWrapper); + return R.ok(); + } + + public R renameAudioFile(AppFileRenameDto bo) { + UpdateWrapper updateWrapper = new UpdateWrapper<>(); + updateWrapper.eq("id",bo.getId()); + updateWrapper.set("re_name",bo.getFileName()); + appBusinessFileMapper.update(updateWrapper); + return R.ok(); + } } \ No newline at end of file diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/base/MqttRuleEngine.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/base/MqttRuleEngine.java index 05914700..15412aab 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/base/MqttRuleEngine.java +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/base/MqttRuleEngine.java @@ -1,9 +1,5 @@ package com.fuyuanshen.global.mqtt.base; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.core.task.TaskExecutor; -import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.stereotype.Component; import java.util.Comparator; @@ -16,9 +12,9 @@ import java.util.List; @Component public class MqttRuleEngine { - @Autowired - @Qualifier("threadPoolTaskExecutor") - private ThreadPoolTaskExecutor threadPoolTaskExecutor; +// @Autowired +// @Qualifier("threadPoolTaskExecutor") +// private ThreadPoolTaskExecutor threadPoolTaskExecutor; private final LinkedHashMap rulesMap = new LinkedHashMap<>(); @@ -41,7 +37,8 @@ public class MqttRuleEngine { int commandType = context.getCommandType(); MqttMessageRule mqttMessageRule = rulesMap.get("Light_" + commandType); if (mqttMessageRule != null) { - threadPoolTaskExecutor.execute(() -> mqttMessageRule.execute(context)); +// threadPoolTaskExecutor.execute(() -> mqttMessageRule.execute(context)); + mqttMessageRule.execute(context); return true; } diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/base/NewMqttMessageRule.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/base/NewMqttMessageRule.java new file mode 100644 index 00000000..754332e4 --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/base/NewMqttMessageRule.java @@ -0,0 +1,26 @@ +package com.fuyuanshen.global.mqtt.base; + +/** + * MQTT消息处理接口 + */ +public interface NewMqttMessageRule { + + /** + * 获取命令类型 + * @return 命令类型 + */ + String getCommandType(); + /** + * 执行处理 + * @param context 处理上下文 + */ + void execute(NewMqttRuleContext context); + + /** + * 获取优先级,数值越小优先级越高 + * @return 优先级 + */ + default int getPriority() { + return 0; + } +} diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/base/NewMqttRuleContext.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/base/NewMqttRuleContext.java new file mode 100644 index 00000000..28b8258c --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/base/NewMqttRuleContext.java @@ -0,0 +1,25 @@ +package com.fuyuanshen.global.mqtt.base; + +import lombok.Data; + +import java.util.Map; + +/** + * MQTT消息处理上下文 + */ +@Data +public class NewMqttRuleContext { + /** + * 命令类型 + */ + private String commandType; + /** + * 设备IMEI + */ + private String deviceImei; + + /** + * MQTT消息负载字典 + */ + private Map payloadDict; +} diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/base/NewMqttRuleEngine.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/base/NewMqttRuleEngine.java new file mode 100644 index 00000000..017b172b --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/base/NewMqttRuleEngine.java @@ -0,0 +1,44 @@ +package com.fuyuanshen.global.mqtt.base; + +import org.springframework.stereotype.Component; + +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; + +/** + * MQTT消息引擎 + */ +@Component +public class NewMqttRuleEngine { + +// @Autowired +// @Qualifier("threadPoolTaskExecutor") +// private ThreadPoolTaskExecutor threadPoolTaskExecutor; + + private final LinkedHashMap rulesMap = new LinkedHashMap<>(); + public NewMqttRuleEngine(List rules) { + // 按优先级排序 + rules.sort(Comparator.comparing(NewMqttMessageRule::getPriority)); + rules.forEach(rule -> rulesMap.put(rule.getCommandType(), rule) + ); + } + + /** + * 执行匹配 + * @param context 处理上下文 + * @return + */ + public boolean executeRule(NewMqttRuleContext context) { + String commandType = context.getCommandType(); +// String funcType = context.getFuncType(); + NewMqttMessageRule mqttMessageRule = rulesMap.get(commandType); + if (mqttMessageRule != null) { +// threadPoolTaskExecutor.execute(() -> mqttMessageRule.execute(context)); + mqttMessageRule.execute(context); + return true; + } + + return false; + } +} diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/config/MqttInboundConfiguration.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/config/MqttInboundConfiguration.java index 102005ca..ead8e0b4 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/config/MqttInboundConfiguration.java +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/config/MqttInboundConfiguration.java @@ -44,12 +44,12 @@ public class MqttInboundConfiguration { if (url == null) { throw new IllegalStateException("MQTT服务器URL未配置"); } - + String subTopic = mqttPropertiesConfig.getSubTopic(); MqttPahoMessageDrivenChannelAdapter mqttPahoMessageDrivenChannelAdapter = new MqttPahoMessageDrivenChannelAdapter( url, clientId, mqttPahoClientFactory, - mqttPropertiesConfig.getSubTopic().split(",") + subTopic.split(",") ); mqttPahoMessageDrivenChannelAdapter.setQos(1); mqttPahoMessageDrivenChannelAdapter.setConverter(new DefaultPahoMessageConverter()); diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/config/MqttOutboundConfiguration.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/config/MqttOutboundConfiguration.java index 84a56082..e969042e 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/config/MqttOutboundConfiguration.java +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/config/MqttOutboundConfiguration.java @@ -47,7 +47,7 @@ public class MqttOutboundConfiguration { mqttPahoClientFactory ); mqttPahoMessageHandler.setDefaultQos(1); - mqttPahoMessageHandler.setDefaultTopic("B/#"); + mqttPahoMessageHandler.setDefaultTopic(mqttPropertiesConfig.getPubTopic()); mqttPahoMessageHandler.setAsync(true); return mqttPahoMessageHandler; } diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/config/MqttPropertiesConfig.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/config/MqttPropertiesConfig.java index 6c5d7db9..be4c69fe 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/config/MqttPropertiesConfig.java +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/config/MqttPropertiesConfig.java @@ -14,6 +14,8 @@ public class MqttPropertiesConfig { private String url; private String subClientId; private String subTopic; + private String subTopic2; private String pubClientId; private String pubTopic; + private String pubTopic2; } \ No newline at end of file diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/config/NewMqttInboundConfiguration.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/config/NewMqttInboundConfiguration.java new file mode 100644 index 00000000..f11544e6 --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/config/NewMqttInboundConfiguration.java @@ -0,0 +1,68 @@ +//package com.fuyuanshen.global.mqtt.config; +// +// +//import cn.hutool.core.lang.UUID; +//import com.fuyuanshen.global.mqtt.receiver.NewReceiverMessageHandler; +//import lombok.extern.slf4j.Slf4j; +//import org.springframework.beans.factory.annotation.Autowired; +//import org.springframework.context.annotation.Bean; +//import org.springframework.context.annotation.Configuration; +//import org.springframework.integration.annotation.ServiceActivator; +//import org.springframework.integration.channel.DirectChannel; +//import org.springframework.integration.core.MessageProducer; +//import org.springframework.integration.mqtt.core.MqttPahoClientFactory; +//import org.springframework.integration.mqtt.inbound.MqttPahoMessageDrivenChannelAdapter; +//import org.springframework.integration.mqtt.support.DefaultPahoMessageConverter; +//import org.springframework.messaging.MessageChannel; +//import org.springframework.messaging.MessageHandler; +// +// +//@Configuration +//@Slf4j +//public class NewMqttInboundConfiguration { +// @Autowired +// private MqttPropertiesConfig mqttPropertiesConfig; +// @Autowired +// private MqttPahoClientFactory mqttPahoClientFactory; +// @Autowired +// private NewReceiverMessageHandler receiverMessageHandler2; +// //消息通道 +// @Bean +// public MessageChannel messageInboundChannel2(){ +// return new DirectChannel(); +// } +// +// /** +// * 配置入站适配器 +// * 作用: 设置订阅主题,以及指定消息的通道 等相关属性 +// * */ +// @Bean +// public MessageProducer messageProducer2(){ +// // 生成一个不重复的随机数 +// String clientId = mqttPropertiesConfig.getSubClientId() + "_" + UUID.fastUUID(); +// String subTopic = mqttPropertiesConfig.getSubTopic2(); +// log.info("订阅主题:{}", subTopic); +// MqttPahoMessageDrivenChannelAdapter mqttPahoMessageDrivenChannelAdapter = new MqttPahoMessageDrivenChannelAdapter( +// mqttPropertiesConfig.getUrl(), +// clientId, +// mqttPahoClientFactory, +// subTopic.split(",") +// ); +// mqttPahoMessageDrivenChannelAdapter.setQos(1); +// mqttPahoMessageDrivenChannelAdapter.setConverter(new DefaultPahoMessageConverter()); +// mqttPahoMessageDrivenChannelAdapter.setOutputChannel(messageInboundChannel2()); +// return mqttPahoMessageDrivenChannelAdapter; +// } +// /** 指定处理消息来自哪个通道 */ +// @Bean +// @ServiceActivator(inputChannel = "messageInboundChannel2") +// public MessageHandler messageHandler2(){ +// return receiverMessageHandler2; +// } +// +// // @Bean +// // @ServiceActivator(inputChannel = "messageInboundChannel") // 确保通道名称正确 +// // public MessageHandler deviceAlarmMessageHandler() { +// // return new DeviceAlrmMessageHandler(); +// // } +//} \ No newline at end of file diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/config/NewMqttOutboundConfiguration.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/config/NewMqttOutboundConfiguration.java new file mode 100644 index 00000000..16a63d82 --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/config/NewMqttOutboundConfiguration.java @@ -0,0 +1,46 @@ +//package com.fuyuanshen.global.mqtt.config; +// +//import cn.hutool.core.lang.UUID; +//import lombok.extern.slf4j.Slf4j; +//import org.springframework.beans.factory.annotation.Autowired; +//import org.springframework.context.annotation.Bean; +//import org.springframework.context.annotation.Configuration; +//import org.springframework.integration.annotation.ServiceActivator; +//import org.springframework.integration.channel.DirectChannel; +//import org.springframework.integration.mqtt.core.MqttPahoClientFactory; +//import org.springframework.integration.mqtt.outbound.MqttPahoMessageHandler; +//import org.springframework.messaging.MessageChannel; +//import org.springframework.messaging.MessageHandler; +// +//@Configuration +//@Slf4j +//public class NewMqttOutboundConfiguration { +// @Autowired +// private MqttPropertiesConfig mqttPropertiesConfig; +// @Autowired +// private MqttPahoClientFactory mqttPahoClientFactory; +// +// // 消息通道 +// @Bean +// public MessageChannel mqttOutboundChannel2(){ +// return new DirectChannel(); +// } +// +// +// /** 配置出站消息处理器 */ +// @Bean +// @ServiceActivator(inputChannel = "mqttOutboundChannel2") // 指定处理器针对哪个通道的消息进行处理 +// public MessageHandler mqttOutboundMessageHandler2(){ +// String clientId = mqttPropertiesConfig.getPubClientId() + "_" + UUID.fastUUID(); +// MqttPahoMessageHandler mqttPahoMessageHandler = new MqttPahoMessageHandler( +// mqttPropertiesConfig.getUrl(), +// clientId, +// mqttPahoClientFactory +// ); +// mqttPahoMessageHandler.setDefaultQos(1); +// mqttPahoMessageHandler.setDefaultTopic(mqttPropertiesConfig.getPubTopic2()); +// mqttPahoMessageHandler.setAsync(true); +// return mqttPahoMessageHandler; +// } +// +//} \ No newline at end of file diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/config/RegisEquipMqttInboundConfiguration.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/config/RegisEquipMqttInboundConfiguration.java new file mode 100644 index 00000000..5bc45d1d --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/config/RegisEquipMqttInboundConfiguration.java @@ -0,0 +1,69 @@ +package com.fuyuanshen.global.mqtt.config; + + +import cn.hutool.core.lang.UUID; +//import com.fuyuanshen.global.mqtt.receiver.NewReceiverMessageHandler; +import com.fuyuanshen.global.mqtt.receiver.RegisEquipReceiverMessageHandler; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.integration.annotation.ServiceActivator; +import org.springframework.integration.channel.DirectChannel; +import org.springframework.integration.core.MessageProducer; +import org.springframework.integration.mqtt.core.MqttPahoClientFactory; +import org.springframework.integration.mqtt.inbound.MqttPahoMessageDrivenChannelAdapter; +import org.springframework.integration.mqtt.support.DefaultPahoMessageConverter; +import org.springframework.messaging.MessageChannel; +import org.springframework.messaging.MessageHandler; + + +@Configuration +@Slf4j +public class RegisEquipMqttInboundConfiguration { + @Autowired + private MqttPropertiesConfig mqttPropertiesConfig; + @Autowired + private MqttPahoClientFactory mqttPahoClientFactory; + @Autowired + private RegisEquipReceiverMessageHandler receiverMessageHandler3; + //消息通道 + @Bean + public MessageChannel messageInboundChannel3(){ + return new DirectChannel(); + } + + /** + * 配置入站适配器 + * 作用: 设置订阅主题,以及指定消息的通道 等相关属性 + * */ + @Bean + public MessageProducer messageProducer3(){ + // 生成一个不重复的随机数 + String clientId = mqttPropertiesConfig.getSubClientId() + "_" + UUID.fastUUID(); + String subTopic = mqttPropertiesConfig.getSubTopic2(); + log.info("订阅主题:{}", subTopic); + MqttPahoMessageDrivenChannelAdapter mqttPahoMessageDrivenChannelAdapter = new MqttPahoMessageDrivenChannelAdapter( + mqttPropertiesConfig.getUrl(), + clientId, + mqttPahoClientFactory, + subTopic.split(",") + ); + mqttPahoMessageDrivenChannelAdapter.setQos(1); + mqttPahoMessageDrivenChannelAdapter.setConverter(new DefaultPahoMessageConverter()); + mqttPahoMessageDrivenChannelAdapter.setOutputChannel(messageInboundChannel3()); + return mqttPahoMessageDrivenChannelAdapter; + } + /** 指定处理消息来自哪个通道 */ + @Bean + @ServiceActivator(inputChannel = "messageInboundChannel3") + public MessageHandler messageHandler3(){ + return receiverMessageHandler3; + } + + // @Bean + // @ServiceActivator(inputChannel = "messageInboundChannel") // 确保通道名称正确 + // public MessageHandler deviceAlarmMessageHandler() { + // return new DeviceAlrmMessageHandler(); + // } +} \ No newline at end of file diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/config/RegisEquipMqttOutboundConfiguration.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/config/RegisEquipMqttOutboundConfiguration.java new file mode 100644 index 00000000..0243ab0b --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/config/RegisEquipMqttOutboundConfiguration.java @@ -0,0 +1,46 @@ +package com.fuyuanshen.global.mqtt.config; + +import cn.hutool.core.lang.UUID; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.integration.annotation.ServiceActivator; +import org.springframework.integration.channel.DirectChannel; +import org.springframework.integration.mqtt.core.MqttPahoClientFactory; +import org.springframework.integration.mqtt.outbound.MqttPahoMessageHandler; +import org.springframework.messaging.MessageChannel; +import org.springframework.messaging.MessageHandler; + +@Configuration +@Slf4j +public class RegisEquipMqttOutboundConfiguration { + @Autowired + private MqttPropertiesConfig mqttPropertiesConfig; + @Autowired + private MqttPahoClientFactory mqttPahoClientFactory; + + // 消息通道 + @Bean + public MessageChannel mqttOutboundChannel3(){ + return new DirectChannel(); + } + + + /** 配置出站消息处理器 */ + @Bean + @ServiceActivator(inputChannel = "mqttOutboundChannel3") // 指定处理器针对哪个通道的消息进行处理 + public MessageHandler mqttOutboundMessageHandler3(){ + String clientId = mqttPropertiesConfig.getPubClientId() + "_" + UUID.fastUUID(); + MqttPahoMessageHandler mqttPahoMessageHandler = new MqttPahoMessageHandler( + mqttPropertiesConfig.getUrl(), + clientId, + mqttPahoClientFactory + ); + mqttPahoMessageHandler.setDefaultQos(1); + mqttPahoMessageHandler.setDefaultTopic(mqttPropertiesConfig.getPubTopic2()); + mqttPahoMessageHandler.setAsync(true); + return mqttPahoMessageHandler; + } + +} \ No newline at end of file diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/constants/MqttConstants.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/constants/MqttConstants.java index 6c9cd76d..b828104b 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/constants/MqttConstants.java +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/constants/MqttConstants.java @@ -13,4 +13,5 @@ public interface MqttConstants { */ String GLOBAL_SUB_KEY = "A/"; + String GLOBAL_PUB_KEY2 = "command/"; } diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/publish/DeviceDataController.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/publish/DeviceDataController.java index 8dacb880..a39e8990 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/publish/DeviceDataController.java +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/publish/DeviceDataController.java @@ -1,20 +1,22 @@ package com.fuyuanshen.global.mqtt.publish; +import cn.dev33.satoken.annotation.SaIgnore; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController -@RequestMapping("/api/") +@RequestMapping("/api") +@SaIgnore @Slf4j public class DeviceDataController { @Autowired private MqttClientTest mqttClientTest; - -// @PostMapping("/{deviceId}/command") + @GetMapping("/command") public ResponseEntity sendCommand() { mqttClientTest.sendMsg(); diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/publish/MqttClientTest.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/publish/MqttClientTest.java index 776b4997..4cf3cb22 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/publish/MqttClientTest.java +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/publish/MqttClientTest.java @@ -13,10 +13,10 @@ public class MqttClientTest { private MqttGateway mqttGateway; public void sendMsg() { - mqttGateway.sendMsgToMqtt("worker/location/1", "hello mqtt spring boot"); + mqttGateway.sendMsgToMqtt("command/894078/HBY670/864865082081523", "hello mqtt spring boot"); log.info("message is send"); - mqttGateway.sendMsgToMqtt("worker/alert/2", "hello mqtt spring boot2"); + mqttGateway.sendMsgToMqtt("report/894078/HBY670/864865082081523", "hello mqtt spring boot2"); log.info("message is send2"); } } \ No newline at end of file diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/receiver/NewReceiverMessageHandler.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/receiver/NewReceiverMessageHandler.java new file mode 100644 index 00000000..696e27d0 --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/receiver/NewReceiverMessageHandler.java @@ -0,0 +1,125 @@ +//package com.fuyuanshen.global.mqtt.receiver; +// +//import cn.hutool.core.lang.Dict; +//import com.baomidou.lock.LockTemplate; +//import com.fuyuanshen.common.core.constant.GlobalConstants; +//import com.fuyuanshen.common.core.utils.StringUtils; +//import com.fuyuanshen.common.json.utils.JsonUtils; +//import com.fuyuanshen.common.redis.utils.RedisUtils; +//import com.fuyuanshen.global.mqtt.base.NewMqttRuleContext; +//import com.fuyuanshen.global.mqtt.base.NewMqttRuleEngine; +//import com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants; +//import com.fuyuanshen.global.queue.MqttMessageQueueConstants; +//import lombok.extern.slf4j.Slf4j; +//import org.redisson.api.RLock; +//import org.redisson.api.RedissonClient; +//import org.springframework.beans.factory.annotation.Autowired; +//import org.springframework.messaging.Message; +//import org.springframework.messaging.MessageHandler; +//import org.springframework.messaging.MessageHeaders; +//import org.springframework.messaging.MessagingException; +//import org.springframework.stereotype.Service; +// +//import java.time.Duration; +//import java.util.Objects; +//import java.util.concurrent.TimeUnit; +// +//import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.DEVICE_KEY_PREFIX; +// +//@Service +//@Slf4j +//public class NewReceiverMessageHandler implements MessageHandler { +// +// @Autowired +// private NewMqttRuleEngine newRuleEngine; +// +// @Override +// public void handleMessage(Message message) throws MessagingException { +// +// Object payload = message.getPayload(); +// MessageHeaders headers = message.getHeaders(); +// String receivedTopic = Objects.requireNonNull(headers.get("mqtt_receivedTopic")).toString(); +// String receivedQos = Objects.requireNonNull(headers.get("mqtt_receivedQos")).toString(); +// String timestamp = Objects.requireNonNull(headers.get("timestamp")).toString(); +// +// log.info("MQTT2 payload= {} \n receivedTopic = {} \n receivedQos = {} \n timestamp = {}", +// payload, receivedTopic, receivedQos, timestamp); +// +// Dict payloadDict = JsonUtils.parseMap(payload.toString()); +// if (receivedTopic == null || payloadDict == null) { +// return; +// } +// String imei = payloadDict.getStr("imei"); +// String funcType = payloadDict.getStr("funcType"); +// // 执行业务逻辑 +// if(StringUtils.isNotBlank(imei)){ +// String queueKey = MqttMessageQueueConstants.MQTT_MESSAGE_QUEUE_KEY; +// String dedupKey = MqttMessageQueueConstants.MQTT_MESSAGE_DEDUP_KEY; +// RedisUtils.offerDeduplicated(queueKey,dedupKey,imei, Duration.ofSeconds(900)); +// //在线状态 +// String deviceOnlineStatusRedisKey = GlobalConstants.GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX+ imei + DeviceRedisKeyConstants.DEVICE_ONLINE_STATUS_KEY_PREFIX ; +// RedisUtils.setCacheObject(deviceOnlineStatusRedisKey, "1", Duration.ofSeconds(360)); +// } +// +// String[] topicArr = receivedTopic.split("/"); +// +// NewMqttRuleContext context = new NewMqttRuleContext(); +// context.setCommandType(topicArr[2]+"_"+funcType); +// context.setDeviceImei(imei); +// context.setPayloadDict(payloadDict); +// +// boolean ruleExecuted = newRuleEngine.executeRule(context); +// +// if (!ruleExecuted) { +// log.warn("未找到匹配的规则来处理命令类型: {}", topicArr[2] + " : " +funcType); +// } +//// final LockInfo lockInfo = lockTemplate.lock(GlobalConstants.GLOBAL_REDIS_KEY + lockKey + imei, 100L, 3000L, RedissonLockExecutor.class); +//// if (null == lockInfo) { +//// log.info("MQTT3业务处理中,请稍后再试:funcType=>{},imei=>{}",funcType,imei); +//// return; +//// } +////// 获取锁成功,处理业务 +//// try { +//// if(StringUtils.isNotBlank(imei)){ +//// String queueKey = MqttMessageQueueConstants.MQTT_MESSAGE_QUEUE_KEY; +//// String dedupKey = MqttMessageQueueConstants.MQTT_MESSAGE_DEDUP_KEY; +//// RedisUtils.offerDeduplicated(queueKey,dedupKey,imei, Duration.ofSeconds(900)); +//// //在线状态 +//// String deviceOnlineStatusRedisKey = GlobalConstants.GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX+ imei + DeviceRedisKeyConstants.DEVICE_ONLINE_STATUS_KEY_PREFIX ; +//// RedisUtils.setCacheObject(deviceOnlineStatusRedisKey, "1", Duration.ofSeconds(360)); +//// } +//// +//// String[] topicArr = receivedTopic.split("/"); +//// +//// NewMqttRuleContext context = new NewMqttRuleContext(); +//// context.setCommandType(topicArr[2]+"_"+funcType); +//// context.setDeviceImei(imei); +//// context.setPayloadDict(payloadDict); +//// +//// boolean ruleExecuted = newRuleEngine.executeRule(context); +//// +//// if (!ruleExecuted) { +//// log.warn("未找到匹配的规则来处理命令类型: {}", topicArr[2] + " : " +funcType); +//// } +//// } finally { +//// //释放锁 +//// lockTemplate.releaseLock(lockInfo); +//// } +// +// +// /* ===== 追加:根据报文内容识别格式并统一解析 ===== */ +//// int intType = MqttXinghanCommandType.computeVirtualCommandType(payloadDict); +//// if (intType > 0) { +//// MqttRuleContext newCtx = new MqttRuleContext(); +//// String commandType = "Light_"+intType; +//// newCtx.setCommandType(commandType); +//// newCtx.setDeviceImei(imei); +//// newCtx.setPayloadDict(payloadDict); +//// +//// boolean ok = ruleEngine.executeRule(newCtx); +//// if (!ok) { +//// log.warn("新规则引擎未命中, imei={}", imei); +//// } +//// } +// } +//} diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/receiver/ReceiverMessageHandler.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/receiver/ReceiverMessageHandler.java index 5b266129..3f9ec3f9 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/receiver/ReceiverMessageHandler.java +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/receiver/ReceiverMessageHandler.java @@ -6,12 +6,12 @@ import com.fuyuanshen.common.core.utils.ImageToCArrayConverter; import com.fuyuanshen.common.core.utils.StringUtils; import com.fuyuanshen.common.json.utils.JsonUtils; import com.fuyuanshen.common.redis.utils.RedisUtils; -import com.fuyuanshen.global.mqtt.base.MqttRuleContext; -import com.fuyuanshen.global.mqtt.base.MqttRuleEngine; -import com.fuyuanshen.global.mqtt.base.MqttXinghanCommandType; +import com.fuyuanshen.global.mqtt.base.*; import com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants; import com.fuyuanshen.global.queue.MqttMessageQueueConstants; import lombok.extern.slf4j.Slf4j; +import org.redisson.api.RLock; +import org.redisson.api.RedissonClient; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.messaging.Message; import org.springframework.messaging.MessageHandler; @@ -21,6 +21,7 @@ import org.springframework.stereotype.Service; import java.time.Duration; import java.util.Objects; +import java.util.concurrent.TimeUnit; import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.DEVICE_KEY_PREFIX; @@ -31,6 +32,9 @@ public class ReceiverMessageHandler implements MessageHandler { @Autowired private MqttRuleEngine ruleEngine; + @Autowired + private NewMqttRuleEngine newRuleEngine; + @Override public void handleMessage(Message message) throws MessagingException { Object payload = message.getPayload(); @@ -38,57 +42,118 @@ public class ReceiverMessageHandler implements MessageHandler { String receivedTopic = Objects.requireNonNull(headers.get("mqtt_receivedTopic")).toString(); String receivedQos = Objects.requireNonNull(headers.get("mqtt_receivedQos")).toString(); String timestamp = Objects.requireNonNull(headers.get("timestamp")).toString(); - + log.info("MQTT payload= {} \n receivedTopic = {} \n receivedQos = {} \n timestamp = {}", payload, receivedTopic, receivedQos, timestamp); - + Dict payloadDict = JsonUtils.parseMap(payload.toString()); if (receivedTopic == null || payloadDict == null) { return; } + + // 模版格式匹配 + boolean flag = checkTemplateFormatMatching(payloadDict); + + String[] subStr = receivedTopic.split("/"); - String deviceImei = subStr[1]; - String state = payloadDict.getStr("state"); - Object[] convertArr = ImageToCArrayConverter.convertByteStringToMixedObjectArray(state); - if(StringUtils.isNotBlank(deviceImei)){ - String queueKey = MqttMessageQueueConstants.MQTT_MESSAGE_QUEUE_KEY; - String dedupKey = MqttMessageQueueConstants.MQTT_MESSAGE_DEDUP_KEY; - RedisUtils.offerDeduplicated(queueKey,dedupKey,deviceImei, Duration.ofSeconds(900)); - //在线状态 - String deviceOnlineStatusRedisKey = GlobalConstants.GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX+ deviceImei + DeviceRedisKeyConstants.DEVICE_ONLINE_STATUS_KEY_PREFIX ; - RedisUtils.setCacheObject(deviceOnlineStatusRedisKey, "1", Duration.ofSeconds(360)); + String deviceImei = null; + if(flag){ + deviceImei = payloadDict.getStr("imei"); + } else { + deviceImei = subStr[1]; } + RedissonClient client = RedisUtils.getClient(); + String lockKey = "mqtt:consumer:lock:"; + String KEY = GlobalConstants.GLOBAL_REDIS_KEY + lockKey + deviceImei; + RLock lock = client.getLock(KEY); - - if (convertArr.length > 0) { - Byte val1 = (Byte) convertArr[0]; - MqttRuleContext context = new MqttRuleContext(); - context.setCommandType(val1); - context.setConvertArr(convertArr); - context.setDeviceImei(deviceImei); - context.setPayloadDict(payloadDict); + try { + // 尝试获取锁, + boolean isLocked = lock.tryLock(60, 3000, TimeUnit.MILLISECONDS); + if (isLocked) { + String state = payloadDict.getStr("state"); + Object[] convertArr = ImageToCArrayConverter.convertByteStringToMixedObjectArray(state); + if(StringUtils.isNotBlank(deviceImei)){ + String queueKey = MqttMessageQueueConstants.MQTT_MESSAGE_QUEUE_KEY; + String dedupKey = MqttMessageQueueConstants.MQTT_MESSAGE_DEDUP_KEY; + RedisUtils.offerDeduplicated(queueKey,dedupKey,deviceImei, Duration.ofSeconds(900)); + //在线状态 + String deviceOnlineStatusRedisKey = GlobalConstants.GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX+ deviceImei + DeviceRedisKeyConstants.DEVICE_ONLINE_STATUS_KEY_PREFIX ; + RedisUtils.setCacheObject(deviceOnlineStatusRedisKey, "1", Duration.ofSeconds(360)); + } - boolean ruleExecuted = ruleEngine.executeRule(context); - - if (!ruleExecuted) { - log.warn("未找到匹配的规则来处理命令类型: {}", val1); + // 新的通信协议 + if(flag){ + String[] topicArr = receivedTopic.split("/"); + String funcType = payloadDict.getStr("funcType"); + NewMqttRuleContext context = new NewMqttRuleContext(); + context.setCommandType(topicArr[2]+"_"+funcType); + context.setDeviceImei(deviceImei); + context.setPayloadDict(payloadDict); + + boolean ruleExecuted = newRuleEngine.executeRule(context); + + if (!ruleExecuted) { + log.warn("未找到匹配的规则来处理命令类型: {}", topicArr[2] + " : " +funcType); + } + return; + } + + if (convertArr.length > 0) { + Byte val1 = (Byte) convertArr[0]; + MqttRuleContext context = new MqttRuleContext(); + context.setCommandType(val1); + context.setConvertArr(convertArr); + context.setDeviceImei(deviceImei); + context.setPayloadDict(payloadDict); + + boolean ruleExecuted = ruleEngine.executeRule(context); + + if (!ruleExecuted) { + log.warn("未找到匹配的规则来处理命令类型: {}", val1); + } + return; + } + + /* ===== 追加:根据报文内容识别格式并统一解析 ===== */ + int intType = MqttXinghanCommandType.computeVirtualCommandType(payloadDict); + if (intType > 0) { + MqttRuleContext newCtx = new MqttRuleContext(); + newCtx.setCommandType((byte) intType); + newCtx.setDeviceImei(deviceImei); + newCtx.setPayloadDict(payloadDict); + + boolean ok = ruleEngine.executeRule(newCtx); + if (!ok) { + log.warn("新规则引擎未命中, imei={}", deviceImei); + } + } + }else{ + log.warn("MQTT获取锁失败,请稍后再试"); } - } - - /* ===== 追加:根据报文内容识别格式并统一解析 ===== */ - int intType = MqttXinghanCommandType.computeVirtualCommandType(payloadDict); - if (intType > 0) { - MqttRuleContext newCtx = new MqttRuleContext(); - newCtx.setCommandType((byte) intType); - newCtx.setDeviceImei(deviceImei); - newCtx.setPayloadDict(payloadDict); - - boolean ok = ruleEngine.executeRule(newCtx); - if (!ok) { - log.warn("新规则引擎未命中, imei={}", deviceImei); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } finally { + // 释放锁 + if (lock.isHeldByCurrentThread()) { + lock.unlock(); } } } + private boolean checkTemplateFormatMatching(Dict payloadDict) { + // 检查是否包含指定的 key + boolean hasImei = payloadDict.containsKey("imei"); + boolean hasFuncType = payloadDict.containsKey("funcType"); + boolean hasStatus = payloadDict.containsKey("status"); + boolean hasTimestamp = payloadDict.containsKey("timestamp"); + boolean hasData = payloadDict.containsKey("data"); + + // 输出检查结果 + log.info("包含 imei: {}, funcType: {}, status: {}, timestamp: {}, data: {}", + hasImei, hasFuncType, hasStatus, hasTimestamp, hasData); + return hasImei && hasFuncType && hasStatus && hasTimestamp && hasData; + } + } diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/receiver/RegisEquipReceiverMessageHandler.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/receiver/RegisEquipReceiverMessageHandler.java new file mode 100644 index 00000000..487f9cd5 --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/receiver/RegisEquipReceiverMessageHandler.java @@ -0,0 +1,156 @@ +package com.fuyuanshen.global.mqtt.receiver; + +import cn.hutool.core.lang.Dict; +import cn.hutool.json.JSONObject; +import com.fuyuanshen.common.core.constant.GlobalConstants; +import com.fuyuanshen.common.core.utils.StringUtils; +import com.fuyuanshen.common.json.utils.JsonUtils; +import com.fuyuanshen.common.redis.utils.RedisUtils; +import com.fuyuanshen.equipment.domain.Device; +import com.fuyuanshen.equipment.domain.DeviceType; +import com.fuyuanshen.equipment.domain.form.DeviceForm; +import com.fuyuanshen.equipment.domain.query.DeviceTypeQueryCriteria; +import com.fuyuanshen.equipment.mapper.DeviceMapper; +import com.fuyuanshen.equipment.mapper.DeviceTypeMapper; +import com.fuyuanshen.equipment.service.DeviceService; +import com.fuyuanshen.global.mqtt.base.NewMqttRuleContext; +import com.fuyuanshen.global.mqtt.base.NewMqttRuleEngine; +import com.fuyuanshen.global.mqtt.config.MqttGateway; +import com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants; +import com.fuyuanshen.global.queue.MqttMessageQueueConstants; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageHandler; +import org.springframework.messaging.MessageHeaders; +import org.springframework.messaging.MessagingException; +import org.springframework.stereotype.Service; + +import java.time.Duration; +import java.util.Objects; + +import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.DEVICE_KEY_PREFIX; + +/** + * 注册设备消息接收处理 + */ +@Service +@Slf4j +public class RegisEquipReceiverMessageHandler implements MessageHandler { + + @Autowired + private NewMqttRuleEngine newRuleEngine; + + @Autowired + private MqttGateway mqttGateway; + + + @Autowired + private DeviceMapper deviceMapper; + @Autowired + private DeviceService deviceService; + + @Autowired + private DeviceTypeMapper deviceTypeMapper; + + + @Override + public void handleMessage(Message message) throws MessagingException { + + Object payload = message.getPayload(); + MessageHeaders headers = message.getHeaders(); + String receivedTopic = Objects.requireNonNull(headers.get("mqtt_receivedTopic")).toString(); + + // 只处理 regis/equip/# 主题的消息 + if (!receivedTopic.startsWith("regis/equip/")) { + return; + } + + // 从主题中提取设备ID + String[] topicParts = receivedTopic.split("/"); + if (topicParts.length < 3) { + log.warn("Invalid topic format: {}", receivedTopic); + return; + } + String deviceTypeName = topicParts[2]; // HBY100-J + + String receivedQos = Objects.requireNonNull(headers.get("mqtt_receivedQos")).toString(); + String timestamp = Objects.requireNonNull(headers.get("timestamp")).toString(); + + log.info("MQTT3 payload= {} \n receivedTopic = {} \n receivedQos = {} \n timestamp = {}", + payload, receivedTopic, receivedQos, timestamp); + + // 解析JSON payload获取imei和mac + Dict payloadDict = JsonUtils.parseMap(payload.toString()); + if (payloadDict == null) { + log.warn("Failed to parse payload JSON"); + return; + } + + String imei = payloadDict.getStr("imei"); + String mac = payloadDict.getStr("mac"); + + log.info("Extracted IMEI: {}, MAC: {}", imei, mac); + + // 验证必要字段 + if (StringUtils.isEmpty(imei) || StringUtils.isEmpty(mac)) { + log.warn("Missing required fields - IMEI: {}, MAC: {}", imei, mac); + return; + } + + DeviceTypeQueryCriteria criteria = new DeviceTypeQueryCriteria(); + criteria.setTypeName(deviceTypeName); + DeviceType deviceType = deviceTypeMapper.queryByName(criteria); + if (deviceType == null) { + log.warn("Device type not found for name: {}", deviceTypeName); + return; + } + + Device device = deviceMapper.selectDeviceByImei(imei); + if (device != null) { + log.warn("Device already exists for IMEI: {}", imei); + String redisKey = GlobalConstants.GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + + imei + ":regis"; + Object value = RedisUtils.getCacheObject(redisKey); + if (value != null) { + log.warn("regis/imei发送次数频率过快: {}", imei); + return; + } +// sendSuccessMessage(imei); + RedisUtils.setCacheObject(redisKey, payload.toString(), Duration.ofSeconds(10)); + return; + } + + // 提取MAC地址后6位 + String macSuffix = mac.replaceAll(":", "").substring(6); // 43:73:43:33:53:33 -> 335333 + // 构建设备名称和蓝牙名称:设备类型+MAC后6位 + String deviceName = deviceTypeName + "-" + macSuffix; // HBY100-335333 + + DeviceForm deviceForm = new DeviceForm(); + deviceForm.setDeviceImei(imei); + deviceForm.setDeviceMac(mac); + deviceForm.setDeviceName(deviceName); + deviceForm.setBluetoothName(deviceName); + deviceForm.setDeviceType(deviceType.getId()); + + try { + deviceService.addDevice(deviceForm); + } catch (Exception e) { + throw new RuntimeException(e); + } + + sendSuccessMessage(imei); + + } + + private void sendSuccessMessage(String imei) { + // 解析原始JSON数据 + JSONObject originalData = new JSONObject(); + originalData.put("code", 200); + // 发送到MQTT + String topic = "regis/" + imei; + mqttGateway.sendMsgToMqtt(topic, originalData.toString()); + } + + +} diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/bjq/BjqActiveReportingDeviceDataRule.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/bjq/BjqActiveReportingDeviceDataRule.java index bdc82373..50fd65a0 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/bjq/BjqActiveReportingDeviceDataRule.java +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/bjq/BjqActiveReportingDeviceDataRule.java @@ -67,31 +67,29 @@ public class BjqActiveReportingDeviceDataRule implements MqttMessageRule { */ public void asyncSendDeviceDataToRedisWithFuture(String deviceImei, String mainLightMode, String laserLightMode, String batteryPercentage, String chargeState, String batteryRemainingTime) { - CompletableFuture.runAsync(() -> { - try { - // 构造设备状态信息对象 - Map deviceInfo = new LinkedHashMap<>(); - deviceInfo.put("deviceImei", deviceImei); - deviceInfo.put("mainLightMode", mainLightMode); - deviceInfo.put("laserLightMode", laserLightMode); - deviceInfo.put("batteryPercentage", batteryPercentage); - deviceInfo.put("chargeState", chargeState); - deviceInfo.put("batteryRemainingTime", batteryRemainingTime); - deviceInfo.put("timestamp", System.currentTimeMillis()); + try { + // 构造设备状态信息对象 + Map deviceInfo = new LinkedHashMap<>(); + deviceInfo.put("deviceImei", deviceImei); + deviceInfo.put("mainLightMode", mainLightMode); + deviceInfo.put("laserLightMode", laserLightMode); + deviceInfo.put("batteryPercentage", batteryPercentage); + deviceInfo.put("chargeState", chargeState); + deviceInfo.put("batteryRemainingTime", batteryRemainingTime); + deviceInfo.put("timestamp", System.currentTimeMillis()); - // 将设备状态信息存储到Redis中 - String deviceRedisKey = GlobalConstants.GLOBAL_REDIS_KEY+DeviceRedisKeyConstants.DEVICE_KEY_PREFIX + deviceImei + DEVICE_STATUS_KEY_PREFIX; - String deviceInfoJson = JsonUtils.toJsonString(deviceInfo); + // 将设备状态信息存储到Redis中 + String deviceRedisKey = GlobalConstants.GLOBAL_REDIS_KEY+DeviceRedisKeyConstants.DEVICE_KEY_PREFIX + deviceImei + DEVICE_STATUS_KEY_PREFIX; + String deviceInfoJson = JsonUtils.toJsonString(deviceInfo); - // 存储到Redis - RedisUtils.setCacheObject(deviceRedisKey, deviceInfoJson); + // 存储到Redis + RedisUtils.setCacheObject(deviceRedisKey, deviceInfoJson); - log.info("设备状态信息已异步发送到Redis: device={}, mainLightMode={}, laserLightMode={}, batteryPercentage={}", - deviceImei, mainLightMode, laserLightMode, batteryPercentage); - } catch (Exception e) { - log.error("异步发送设备信息到Redis时出错: device={}, error={}", deviceImei, e.getMessage(), e); - } - }); + log.info("设备状态信息已异步发送到Redis: device={}, mainLightMode={}, laserLightMode={}, batteryPercentage={}", + deviceImei, mainLightMode, laserLightMode, batteryPercentage); + } catch (Exception e) { + log.error("异步发送设备信息到Redis时出错: device={}, error={}", deviceImei, e.getMessage(), e); + } } diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/bjq/BjqBootLogoRule.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/bjq/BjqBootLogoRule.java index 2a95db74..651f03a6 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/bjq/BjqBootLogoRule.java +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/bjq/BjqBootLogoRule.java @@ -54,7 +54,7 @@ public class BjqBootLogoRule implements MqttMessageRule { if (StringUtils.isEmpty(data)) { return; } - + byte[] arr = ImageToCArrayConverter.convertStringToByteArray(data); byte[] specificChunk = ImageToCArrayConverter.getChunk(arr, (val2 - 1), 512); log.info("第{}块数据大小: {} 字节", val2, specificChunk.length); @@ -67,10 +67,10 @@ public class BjqBootLogoRule implements MqttMessageRule { intData.add(0); intData.add(0); intData.add(0); - + Map map = new HashMap<>(); map.put("instruct", intData); - + mqttGateway.sendMsgToMqtt(MqttConstants.GLOBAL_PUB_KEY + context.getDeviceImei(), 1, JsonUtils.toJsonString(map)); log.info("发送开机LOGO点阵数据到设备消息=>topic:{},payload:{}", MqttConstants.GLOBAL_PUB_KEY + context.getDeviceImei(), diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/bjq/BjqLocationDataRule.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/bjq/BjqLocationDataRule.java index 5fac7459..e4377e42 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/bjq/BjqLocationDataRule.java +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/bjq/BjqLocationDataRule.java @@ -121,16 +121,15 @@ public class BjqLocationDataRule implements MqttMessageRule { * @param longitude 经度 */ public void asyncSendLocationToRedisWithFuture(String deviceImei, String latitude, String longitude) { - CompletableFuture.runAsync(() -> { - try { - if (StringUtils.isBlank(latitude) || StringUtils.isBlank(longitude)) { - return; - } + try { + if (StringUtils.isBlank(latitude) || StringUtils.isBlank(longitude)) { + return; + } // String[] latArr = latitude.split("\\."); // String[] lonArr = longitude.split("\\."); // // 将位置信息存储到Redis中 - String redisKey = GlobalConstants.GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + deviceImei + DEVICE_LOCATION_KEY_PREFIX; + String redisKey = GlobalConstants.GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + deviceImei + DEVICE_LOCATION_KEY_PREFIX; // String redisObj = RedisUtils.getCacheObject(redisKey); // JSONObject jsonOBj = JSONObject.parseObject(redisObj); // if(jsonOBj != null){ @@ -150,34 +149,33 @@ public class BjqLocationDataRule implements MqttMessageRule { // } // } - // 构造位置信息对象 - Map locationInfo = new LinkedHashMap<>(); - double[] doubles = LngLonUtil.gps84_To_Gcj02(Double.parseDouble(latitude), Double.parseDouble(longitude)); - locationInfo.put("deviceImei", deviceImei); - locationInfo.put("latitude", doubles[0]); - locationInfo.put("longitude", doubles[1]); - locationInfo.put("wgs84_latitude", latitude); - locationInfo.put("wgs84_longitude", longitude); - String address = GetAddressFromLatUtil.getAdd(String.valueOf(doubles[1]), String.valueOf(doubles[0])); - locationInfo.put("address", address); - locationInfo.put("timestamp", System.currentTimeMillis()); + // 构造位置信息对象 + Map locationInfo = new LinkedHashMap<>(); + double[] doubles = LngLonUtil.gps84_To_Gcj02(Double.parseDouble(latitude), Double.parseDouble(longitude)); + locationInfo.put("deviceImei", deviceImei); + locationInfo.put("latitude", doubles[0]); + locationInfo.put("longitude", doubles[1]); + locationInfo.put("wgs84_latitude", latitude); + locationInfo.put("wgs84_longitude", longitude); + String address = GetAddressFromLatUtil.getAdd(String.valueOf(doubles[1]), String.valueOf(doubles[0])); + locationInfo.put("address", address); + locationInfo.put("timestamp", System.currentTimeMillis()); - String locationJson = JsonUtils.toJsonString(locationInfo); + String locationJson = JsonUtils.toJsonString(locationInfo); - // 存储到Redis - RedisUtils.setCacheObject(redisKey, locationJson); + // 存储到Redis + RedisUtils.setCacheObject(redisKey, locationJson); - // 存储到一个列表中,保留历史位置信息 + // 存储到一个列表中,保留历史位置信息 // String locationHistoryKey = GlobalConstants.GLOBAL_REDIS_KEY+DeviceRedisKeyConstants.DEVICE_LOCATION_HISTORY_KEY_PREFIX + deviceImei; // RedisUtils.addCacheList(locationHistoryKey, locationJson); // RedisUtils.expire(locationHistoryKey, Duration.ofDays(90)); - storeDeviceTrajectoryWithSortedSet(deviceImei, locationJson); - log.info("位置信息已异步发送到Redis: device={}, lat={}, lon={}", deviceImei, latitude, longitude); - } catch (Exception e) { - log.error("异步发送位置信息到Redis时出错: device={}, error={}", deviceImei, e.getMessage(), e); - } - }); + storeDeviceTrajectoryWithSortedSet(deviceImei, locationJson); + log.info("位置信息已异步发送到Redis: device={}, lat={}, lon={}", deviceImei, latitude, longitude); + } catch (Exception e) { + log.error("异步发送位置信息到Redis时出错: device={}, error={}", deviceImei, e.getMessage(), e); + } } @@ -189,20 +187,18 @@ public class BjqLocationDataRule implements MqttMessageRule { * @param longitude 经度 */ public void asyncSaveLocationToMySQLWithFuture(String deviceImei, String latitude, String longitude) { - CompletableFuture.runAsync(() -> { - try { - if (StringUtils.isBlank(latitude) || StringUtils.isBlank(longitude)) { - return; - } - - // 调用服务层方法更新设备位置信息 - deviceService.updateDeviceLocationByImei(deviceImei, longitude, latitude); - - log.info("位置信息已异步保存到MySQL: device={}, lat={}, lon={}", deviceImei, latitude, longitude); - } catch (Exception e) { - log.error("异步保存位置信息到MySQL时出错: device={}, error={}", deviceImei, e.getMessage(), e); + try { + if (StringUtils.isBlank(latitude) || StringUtils.isBlank(longitude)) { + return; } - }); + + // 调用服务层方法更新设备位置信息 + deviceService.updateDeviceLocationByImei(deviceImei, longitude, latitude); + + log.info("位置信息已异步保存到MySQL: device={}, lat={}, lon={}", deviceImei, latitude, longitude); + } catch (Exception e) { + log.error("异步保存位置信息到MySQL时出错: device={}, error={}", deviceImei, e.getMessage(), e); + } } diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/bjq/BjqModeRule.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/bjq/BjqModeRule.java index 8f97cec1..c288f58d 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/bjq/BjqModeRule.java +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/bjq/BjqModeRule.java @@ -50,6 +50,8 @@ public class BjqModeRule implements MqttMessageRule { public void execute(MqttRuleContext context) { String functionAccess = FUNCTION_ACCESS_KEY + context.getDeviceImei(); try { + log.info("处理灯光模式命令"); + log.info("MQTT消息负载字典:{}", context.getPayloadDict()); Object[] convertArr = context.getConvertArr(); String mainLightMode = convertArr[1].toString(); diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/bjq/BjqPersonnelInfoDataRule.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/bjq/BjqPersonnelInfoDataRule.java index 49fabe24..acbbc7ee 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/bjq/BjqPersonnelInfoDataRule.java +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/bjq/BjqPersonnelInfoDataRule.java @@ -34,8 +34,8 @@ public class BjqPersonnelInfoDataRule implements MqttMessageRule { public void execute(MqttRuleContext context) { String functionAccess = FUNCTION_ACCESS_KEY + context.getDeviceImei(); try { - Object[] convertArr = context.getConvertArr(); - RedisUtils.setCacheObject(functionAccess, FunctionAccessStatus.OK.getCode(), Duration.ofSeconds(30)); +// Object[] convertArr = context.getConvertArr(); +// RedisUtils.setCacheObject(functionAccess, FunctionAccessStatus.OK.getCode(), Duration.ofSeconds(30)); } catch (Exception e) { log.error("处理定位数据命令时出错", e); RedisUtils.setCacheObject(functionAccess, FunctionAccessStatus.FAILED.getCode(), Duration.ofSeconds(30)); diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/bjq/BjqSendMessageRule.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/bjq/BjqSendMessageRule.java index d2d3620b..028cb3f9 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/bjq/BjqSendMessageRule.java +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/bjq/BjqSendMessageRule.java @@ -78,10 +78,10 @@ public class BjqSendMessageRule implements MqttMessageRule { intData.add(0); intData.add(0); intData.add(0); - + Map map = new HashMap<>(); map.put("instruct", intData); - + mqttGateway.sendMsgToMqtt(MqttConstants.GLOBAL_PUB_KEY + context.getDeviceImei(), 1, JsonUtils.toJsonString(map)); log.info("发送设备信息数据到设备消息=>topic:{},payload:{}", MqttConstants.GLOBAL_PUB_KEY + context.getDeviceImei(), diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/hby100j/FuncType10StrobeMode.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/hby100j/FuncType10StrobeMode.java new file mode 100644 index 00000000..2888b600 --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/hby100j/FuncType10StrobeMode.java @@ -0,0 +1,43 @@ +package com.fuyuanshen.global.mqtt.rule.hby100j; + +import com.alibaba.fastjson2.JSONObject; +import com.fuyuanshen.common.core.constant.GlobalConstants; +import com.fuyuanshen.common.redis.utils.RedisUtils; +import com.fuyuanshen.global.mqtt.base.NewMqttMessageRule; +import com.fuyuanshen.global.mqtt.base.NewMqttRuleContext; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.Map; + +import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.DEVICE_KEY_PREFIX; + +/** + * 爆闪模式开启/关闭 + */ +@Slf4j +@Component +public class FuncType10StrobeMode implements NewMqttMessageRule { + + @Override + public String getCommandType() { + return "HBY100_10"; + } + + @Override + public void execute(NewMqttRuleContext context) { + log.info("HBY100J爆闪模式开启/关闭,消息负载:{}", context.getPayloadDict()); + + try { + String redisKey = GlobalConstants.GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + + context.getDeviceImei() + ":strobeMode"; + + Map payloadDict = context.getPayloadDict(); + if (payloadDict != null) { + RedisUtils.setCacheObject(redisKey, JSONObject.toJSONString(payloadDict)); + } + } catch (Exception e) { + log.error("HBY100J爆闪模式开启/关闭失败", e); + } + } +} diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/hby100j/FuncType11Frequency.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/hby100j/FuncType11Frequency.java new file mode 100644 index 00000000..b5e5370f --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/hby100j/FuncType11Frequency.java @@ -0,0 +1,43 @@ +package com.fuyuanshen.global.mqtt.rule.hby100j; + +import com.alibaba.fastjson2.JSONObject; +import com.fuyuanshen.common.core.constant.GlobalConstants; +import com.fuyuanshen.common.redis.utils.RedisUtils; +import com.fuyuanshen.global.mqtt.base.NewMqttMessageRule; +import com.fuyuanshen.global.mqtt.base.NewMqttRuleContext; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.Map; + +import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.DEVICE_KEY_PREFIX; + +/** + * 修改警示灯爆闪频率 + */ +@Slf4j +@Component +public class FuncType11Frequency implements NewMqttMessageRule { + + @Override + public String getCommandType() { + return "HBY100_11"; + } + + @Override + public void execute(NewMqttRuleContext context) { + log.info("HBY100J修改警示灯爆闪频率,消息负载:{}", context.getPayloadDict()); + + try { + String redisKey = GlobalConstants.GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + + context.getDeviceImei() + ":frequency"; + + Map payloadDict = context.getPayloadDict(); + if (payloadDict != null) { + RedisUtils.setCacheObject(redisKey, JSONObject.toJSONString(payloadDict)); + } + } catch (Exception e) { + log.error("HBY100J修改警示灯爆闪频率失败", e); + } + } +} diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/hby100j/FuncType12ForceAudio.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/hby100j/FuncType12ForceAudio.java new file mode 100644 index 00000000..9558f3b5 --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/hby100j/FuncType12ForceAudio.java @@ -0,0 +1,45 @@ +package com.fuyuanshen.global.mqtt.rule.hby100j; + +import com.alibaba.fastjson2.JSONObject; +import com.fuyuanshen.common.core.constant.GlobalConstants; +import com.fuyuanshen.common.redis.utils.RedisUtils; +import com.fuyuanshen.global.mqtt.base.NewMqttMessageRule; +import com.fuyuanshen.global.mqtt.base.NewMqttRuleContext; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.Map; + +import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.DEVICE_KEY_PREFIX; + +/** + * 强制声光报警开启/关闭 + */ +@Slf4j +@Component +public class FuncType12ForceAudio implements NewMqttMessageRule { + + @Override + public String getCommandType() { + return "HBY100_12"; + } + + @Override + public void execute(NewMqttRuleContext context) { + log.info("HBY100J强制声光报警开启/关闭,消息负载:{}", context.getPayloadDict()); + + try { + // 构建强制声光报警开关的Redis键 + String redisKey = GlobalConstants.GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + + context.getDeviceImei() + ":forceAudio"; + + Map payloadDict = context.getPayloadDict(); + if (payloadDict != null) { + // 存储强制声光报警开关状态到Redis + RedisUtils.setCacheObject(redisKey, JSONObject.toJSONString(payloadDict)); + } + } catch (Exception e) { + log.error("HBY100J强制声光报警开启/关闭失败", e); + } + } +} diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/hby100j/FuncType13Brightness.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/hby100j/FuncType13Brightness.java new file mode 100644 index 00000000..cbd571d1 --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/hby100j/FuncType13Brightness.java @@ -0,0 +1,43 @@ +package com.fuyuanshen.global.mqtt.rule.hby100j; + +import com.alibaba.fastjson2.JSONObject; +import com.fuyuanshen.common.core.constant.GlobalConstants; +import com.fuyuanshen.common.redis.utils.RedisUtils; +import com.fuyuanshen.global.mqtt.base.NewMqttMessageRule; +import com.fuyuanshen.global.mqtt.base.NewMqttRuleContext; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.Map; + +import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.DEVICE_KEY_PREFIX; + +/** + * 警示灯LED亮度调节 + */ +@Slf4j +@Component +public class FuncType13Brightness implements NewMqttMessageRule { + + @Override + public String getCommandType() { + return "HBY100_13"; + } + + @Override + public void execute(NewMqttRuleContext context) { + log.info("HBY100J警示灯LED亮度调节,消息负载:{}", context.getPayloadDict()); + + try { + String redisKey = GlobalConstants.GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + + context.getDeviceImei() + ":brightness"; + + Map payloadDict = context.getPayloadDict(); + if (payloadDict != null) { + RedisUtils.setCacheObject(redisKey, JSONObject.toJSONString(payloadDict)); + } + } catch (Exception e) { + log.error("HBY100J警示灯LED亮度调节失败", e); + } + } +} diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/hby100j/FuncType14Report.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/hby100j/FuncType14Report.java new file mode 100644 index 00000000..0963a43a --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/hby100j/FuncType14Report.java @@ -0,0 +1,43 @@ +package com.fuyuanshen.global.mqtt.rule.hby100j; + +import com.alibaba.fastjson2.JSONObject; +import com.fuyuanshen.common.core.constant.GlobalConstants; +import com.fuyuanshen.common.redis.utils.RedisUtils; +import com.fuyuanshen.global.mqtt.base.NewMqttMessageRule; +import com.fuyuanshen.global.mqtt.base.NewMqttRuleContext; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.Map; + +import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.DEVICE_KEY_PREFIX; + +/** + * 获取警示灯的当前工作方式(设备下发返回响应数据、设备定时主动上报) + */ +@Slf4j +@Component +public class FuncType14Report implements NewMqttMessageRule { + + @Override + public String getCommandType() { + return "HBY100_14"; + } + + @Override + public void execute(NewMqttRuleContext context) { + log.info("HBY100J设备定时主动上报,消息负载:{}", context.getPayloadDict()); + + try { + String redisKey = GlobalConstants.GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + + context.getDeviceImei() + ":report"; + + Map payloadDict = context.getPayloadDict(); + if (payloadDict != null) { + RedisUtils.setCacheObject(redisKey, JSONObject.toJSONString(payloadDict)); + } + } catch (Exception e) { + log.error("HBY100J设备定时主动上报失败", e); + } + } +} diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/hby100j/FuncType1Rest.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/hby100j/FuncType1Rest.java new file mode 100644 index 00000000..22a9085c --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/hby100j/FuncType1Rest.java @@ -0,0 +1,46 @@ +package com.fuyuanshen.global.mqtt.rule.hby100j; + +import com.alibaba.fastjson2.JSONObject; +import com.fuyuanshen.common.core.constant.GlobalConstants; +import com.fuyuanshen.common.redis.utils.RedisUtils; +import com.fuyuanshen.global.mqtt.base.NewMqttMessageRule; +import com.fuyuanshen.global.mqtt.base.NewMqttRuleContext; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.Map; + +import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.DEVICE_KEY_PREFIX; + +/** + * 设备复位 + */ +@Slf4j +@Component +public class FuncType1Rest implements NewMqttMessageRule { + + @Override + public String getCommandType() { + return "HBY100_1"; + } + + @Override + public void execute(NewMqttRuleContext context) { + log.info("开始处理设备复位,消息负载:{}", context.getPayloadDict()); + + try { + // 构建强制声光报警开关的Redis键 + String redisKey = GlobalConstants.GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + + context.getDeviceImei() + ":rest"; + + Map payloadDict = context.getPayloadDict(); + if (payloadDict != null) { + // 存储强制声光报警开关状态到Redis + RedisUtils.setCacheObject(redisKey, JSONObject.toJSONString(payloadDict)); + } + log.info("设备复位,设备ID:{}", context.getDeviceImei()); + } catch (Exception e) { + log.error("处理设备复位失败", e); + } + } +} diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/hby100j/FuncType2BaseInfo.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/hby100j/FuncType2BaseInfo.java new file mode 100644 index 00000000..1ef43c62 --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/hby100j/FuncType2BaseInfo.java @@ -0,0 +1,46 @@ +package com.fuyuanshen.global.mqtt.rule.hby100j; + +import com.alibaba.fastjson2.JSONObject; +import com.fuyuanshen.common.core.constant.GlobalConstants; +import com.fuyuanshen.common.redis.utils.RedisUtils; +import com.fuyuanshen.global.mqtt.base.NewMqttMessageRule; +import com.fuyuanshen.global.mqtt.base.NewMqttRuleContext; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.Map; + +import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.DEVICE_KEY_PREFIX; + +/** + * 获取设备基础信息 + */ +@Slf4j +@Component +public class FuncType2BaseInfo implements NewMqttMessageRule { + + @Override + public String getCommandType() { + return "HBY100_2"; + } + + @Override + public void execute(NewMqttRuleContext context) { + log.info("开始处理强制声光报警开关,消息负载:{}", context.getPayloadDict()); + + try { + // 构建强制声光报警开关的Redis键 + String redisKey = GlobalConstants.GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + + context.getDeviceImei() + ":force_audio_visual_alarm_switch"; + + Map payloadDict = context.getPayloadDict(); + if (payloadDict != null) { + // 存储强制声光报警开关状态到Redis + RedisUtils.setCacheObject(redisKey, JSONObject.toJSONString(payloadDict)); + } + log.info("强制声光报警开关处理完成,设备ID:{}", context.getDeviceImei()); + } catch (Exception e) { + log.error("处理强制声光报警开关失败", e); + } + } +} diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/hby100j/FuncType3Location.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/hby100j/FuncType3Location.java new file mode 100644 index 00000000..f67b24dc --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/hby100j/FuncType3Location.java @@ -0,0 +1,208 @@ +package com.fuyuanshen.global.mqtt.rule.hby100j; + +import com.alibaba.fastjson2.JSONObject; +import com.fuyuanshen.common.core.constant.GlobalConstants; +import com.fuyuanshen.common.core.utils.StringUtils; +import com.fuyuanshen.common.json.utils.JsonUtils; +import com.fuyuanshen.common.redis.utils.RedisUtils; +import com.fuyuanshen.equipment.utils.map.GetAddressFromLatUtil; +import com.fuyuanshen.equipment.utils.map.LngLonUtil; +import com.fuyuanshen.global.mqtt.base.NewMqttMessageRule; +import com.fuyuanshen.global.mqtt.base.NewMqttRuleContext; +import com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants; +import com.fuyuanshen.global.mqtt.rule.hby100j.domin.FunctionType3LocationReport; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.LinkedHashMap; +import java.util.Map; + +import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.DEVICE_KEY_PREFIX; +import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.DEVICE_LOCATION_KEY_PREFIX; + +/** + * 获取设备位置信息(设备下发返回响应数据、设备定时主动上报) + */ +@Slf4j +@Component +public class FuncType3Location implements NewMqttMessageRule { + + @Override + public String getCommandType() { + return "HBY100_3"; + } + + @Override + public void execute(NewMqttRuleContext context) { + log.info("HBY100J获取设备位置信息,消息负载:{}", context.getPayloadDict()); + + try { + String redisKey = GlobalConstants.GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + + context.getDeviceImei() + ":location"; + + Map payloadDict = context.getPayloadDict(); + if (payloadDict != null) { +// RedisUtils.setCacheObject(redisKey, JSONObject.toJSONString(payloadDict)); + String jsonString = JSONObject.toJSONString(payloadDict); + FunctionType3LocationReport data = JSONObject.parseObject(jsonString, FunctionType3LocationReport.class); + FunctionType3LocationReport.Data data1 = data.getData(); + if (data1 != null) { + Double latitude = data1.getLatitude(); + Double longitude = data1.getLongitude(); + asyncSendLocationToRedisWithFuture(context.getDeviceImei(), latitude.toString(), longitude.toString()); + } + } + } catch (Exception e) { + log.error("HBY100J获取设备位置信息失败", e); + } + } + + /** 位置未发生明显变化的距离阈值(米),可通过配置中心动态调整 */ + private final double MOVEMENT_THRESHOLD_METER = 10.0; + + /** + * 异步发送位置信息到Redis(使用CompletableFuture) + * + * @param deviceImei 设备IMEI + * @param latitude 纬度 + * @param longitude 经度 + */ + public void asyncSendLocationToRedisWithFuture(String deviceImei, String latitude, String longitude) { + try { + if (latitude == null || longitude == null) { + return; + } + if (StringUtils.isAnyBlank(deviceImei, latitude, longitude)) { + log.warn("位置上报参数为空,deviceImei={}", deviceImei); + return; + } + //log.info("位置上报,deviceImei={}, lat={}, lon={}", deviceImei, latitude, longitude); + // 1. 解析当前上报的经纬度 + Double curLat = parseDoubleSafe(latitude.trim()); + Double curLon = parseDoubleSafe(longitude.trim()); + if (curLat == null || curLon == null) { + log.warn("经纬度格式错误,直接更新,deviceImei={}, lat={}, lon={}", deviceImei, latitude, longitude); +// doSaveLocation(deviceImei, latitude, longitude); + return; + } + + // 2. 读取 Redis 中缓存的上一次位置 + String redisKey = GlobalConstants.GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + deviceImei + DEVICE_LOCATION_KEY_PREFIX; + String cachedJson = RedisUtils.getCacheObject(redisKey); + + if (StringUtils.isNotBlank(cachedJson)) { + com.alibaba.fastjson2.JSONObject cachedObj = com.alibaba.fastjson2.JSONObject.parseObject(cachedJson); + String cachedWgs84Lat = cachedObj.getString("wgs84_latitude"); + String cachedWgs84Lon = cachedObj.getString("wgs84_longitude"); + + Double oldLat = parseDoubleSafe(cachedWgs84Lat); + Double oldLon = parseDoubleSafe(cachedWgs84Lon); + + if (oldLat != null && oldLon != null) { + double distance = haversine(oldLat, oldLon, curLat, curLon); + if (distance <= MOVEMENT_THRESHOLD_METER) { + log.info("位置未发生明显变化({}米 <= {}米),不更新 Redis,deviceImei={}, lat={}, lon={}", + distance, MOVEMENT_THRESHOLD_METER, deviceImei, latitude, longitude); + return; + } + } + } +// String[] latArr = latitude.split("\\."); +// String[] lonArr = longitude.split("\\."); +// // 将位置信息存储到Redis中 +// String redisKey = GlobalConstants.GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + deviceImei + DEVICE_LOCATION_KEY_PREFIX; +// String redisObj = RedisUtils.getCacheObject(redisKey); +// JSONObject jsonOBj = JSONObject.parseObject(redisObj); +// if(jsonOBj != null){ +// String str1 = latArr[0] +"."+ latArr[1].substring(0,4); +// String str2 = lonArr[0] +"."+ lonArr[1].substring(0,4); +// +// String cacheLatitude = jsonOBj.getString("wgs84_latitude"); +// String cacheLongitude = jsonOBj.getString("wgs84_longitude"); +// String[] latArr1 = cacheLatitude.split("\\."); +// String[] lonArr1 = cacheLongitude.split("\\."); +// +// String cacheStr1 = latArr1[0] +"."+ latArr1[1].substring(0,4); +// String cacheStr2 = lonArr1[0] +"."+ lonArr1[1].substring(0,4); +// if(str1.equals(cacheStr1) && str2.equals(cacheStr2)){ +// log.info("位置信息未发生变化: device={}, lat={}, lon={}", deviceImei, latitude, longitude); +// return; +// } +// } + + // 构造位置信息对象 + Map locationInfo = new LinkedHashMap<>(); + double[] doubles = LngLonUtil.gps84_To_Gcj02(Double.parseDouble(latitude), Double.parseDouble(longitude)); + locationInfo.put("deviceImei", deviceImei); + locationInfo.put("latitude", doubles[0]); + locationInfo.put("longitude", doubles[1]); + locationInfo.put("wgs84_latitude", latitude); + locationInfo.put("wgs84_longitude", longitude); + + + String address = GetAddressFromLatUtil.getAdd(String.valueOf(doubles[1]), String.valueOf(doubles[0])); + locationInfo.put("address", address); + locationInfo.put("timestamp", System.currentTimeMillis()); + + + String locationJson = JsonUtils.toJsonString(locationInfo); + + // 存储到Redis + RedisUtils.setCacheObject(redisKey, locationJson); + + // 存储到一个列表中,保留历史位置信息 +// String locationHistoryKey = GlobalConstants.GLOBAL_REDIS_KEY+DeviceRedisKeyConstants.DEVICE_LOCATION_HISTORY_KEY_PREFIX + deviceImei; +// RedisUtils.addCacheList(locationHistoryKey, locationJson); +// RedisUtils.expire(locationHistoryKey, Duration.ofDays(90)); + storeDeviceTrajectoryWithSortedSet(deviceImei, locationJson); + log.info("位置信息已异步发送到Redis: device={}, lat={}, lon={}", deviceImei, latitude, longitude); + } catch (Exception e) { + log.error("异步发送位置信息到Redis时出错: device={}, error={}", deviceImei, e.getMessage(), e); + } + } + /** 安全解析 double,解析失败返回 null */ + private Double parseDoubleSafe(String str) { + if (StringUtils.isBlank(str)) return null; + try { + return Double.parseDouble(str.trim()); + } catch (NumberFormatException e) { + return null; + } + } + /** Haversine 公式计算两点球面距离(米) */ + private double haversine(double lat1, double lon1, double lat2, double lon2) { + double dLat = Math.toRadians(lat2 - lat1); + double dLon = Math.toRadians(lon2 - lon1); + double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + + Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) * + Math.sin(dLon / 2) * Math.sin(dLon / 2); + double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + /** 地球平均半径(米) */ + double EARTH_RADIUS = 6371_393.0; + return EARTH_RADIUS * c; + } + + /** + * 存储设备30天历史轨迹到Redis (使用Sorted Set) + */ + public void storeDeviceTrajectoryWithSortedSet(String deviceImei, String locationJson) { + try { + String trajectoryKey = GlobalConstants.GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + deviceImei + DeviceRedisKeyConstants.DEVICE_LOCATION_HISTORY_KEY_PREFIX; +// String trajectoryKey = "device:trajectory:zset:" + deviceImei; +// String locationJson = JsonUtils.toJsonString(locationInfo); + long timestamp = System.currentTimeMillis(); + + // 添加到Sorted Set,使用时间戳作为score + RedisUtils.zAdd(trajectoryKey, locationJson, timestamp); + +// // 设置30天过期时间 +// RedisUtils.expire(trajectoryKey, Duration.ofDays(30)); + + // 清理30天前的数据(冗余保护) + long thirtyDaysAgo = System.currentTimeMillis() - (7L * 24 * 60 * 60 * 1000); + RedisUtils.zRemoveRangeByScore(trajectoryKey, 0, thirtyDaysAgo); + } catch (Exception e) { + log.error("存储设备轨迹到Redis(ZSet)失败: device={}, error={}", deviceImei, e.getMessage(), e); + } + } +} diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/hby100j/FuncType4PowerStatus.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/hby100j/FuncType4PowerStatus.java new file mode 100644 index 00000000..21252cbb --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/hby100j/FuncType4PowerStatus.java @@ -0,0 +1,69 @@ +package com.fuyuanshen.global.mqtt.rule.hby100j; + +import com.alibaba.fastjson2.JSONObject; +import com.fuyuanshen.common.core.constant.GlobalConstants; +import com.fuyuanshen.common.json.utils.JsonUtils; +import com.fuyuanshen.common.redis.utils.RedisUtils; +import com.fuyuanshen.global.mqtt.base.NewMqttMessageRule; +import com.fuyuanshen.global.mqtt.base.NewMqttRuleContext; +import com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants; +import com.fuyuanshen.global.mqtt.rule.hby100j.domin.FunctionType4PowerStatusReport; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.LinkedHashMap; +import java.util.Map; + +import static com.fuyuanshen.common.core.constant.GlobalConstants.GLOBAL_REDIS_KEY; +import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.DEVICE_KEY_PREFIX; +import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.DEVICE_STATUS_KEY_PREFIX; + +/** + * 获取设备电源状态(设备下发返回响应数据、设备定时主动上报) + */ +@Slf4j +@Component +public class FuncType4PowerStatus implements NewMqttMessageRule { + + @Override + public String getCommandType() { + return "HBY100_4"; + } + + @Override + public void execute(NewMqttRuleContext context) { + log.info("HBY100J获取设备电源状态,消息负载:{}", context.getPayloadDict()); + + try { + String deviceStatus = RedisUtils.getCacheObject(GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + context.getDeviceImei() + DEVICE_STATUS_KEY_PREFIX); +// String redisKey = GlobalConstants.GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + +// context.getDeviceImei() + ":powerStatus"; + + Map payloadDict = context.getPayloadDict(); + if (payloadDict != null) { + + String str = JSONObject.toJSONString(payloadDict); + FunctionType4PowerStatusReport powerStatusReport =JSONObject.parseObject(str,FunctionType4PowerStatusReport.class); + + FunctionType4PowerStatusReport.Data data = powerStatusReport.getData(); + if(data != null){ + // 构造设备状态信息对象 + Map deviceInfo = new LinkedHashMap<>(); + deviceInfo.put("batteryPercentage", data.getLevel()); + deviceInfo.put("chargeState", data.getCharge()); + deviceInfo.put("batteryRemainingTime", data.getBatteryRemainingTime()); + + // 将设备状态信息存储到Redis中 + String deviceRedisKey = GlobalConstants.GLOBAL_REDIS_KEY+ DeviceRedisKeyConstants.DEVICE_KEY_PREFIX + context.getDeviceImei() + DEVICE_STATUS_KEY_PREFIX; + String deviceInfoJson = JsonUtils.toJsonString(deviceInfo); + + // 存储到Redis + RedisUtils.setCacheObject(deviceRedisKey, deviceInfoJson); + } + + } + } catch (Exception e) { + log.error("HBY100J获取设备电源状态失败", e); + } + } +} diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/hby100j/FuncType5UpdateVoice.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/hby100j/FuncType5UpdateVoice.java new file mode 100644 index 00000000..f219a81e --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/hby100j/FuncType5UpdateVoice.java @@ -0,0 +1,43 @@ +package com.fuyuanshen.global.mqtt.rule.hby100j; + +import com.alibaba.fastjson2.JSONObject; +import com.fuyuanshen.common.core.constant.GlobalConstants; +import com.fuyuanshen.common.redis.utils.RedisUtils; +import com.fuyuanshen.global.mqtt.base.NewMqttMessageRule; +import com.fuyuanshen.global.mqtt.base.NewMqttRuleContext; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.Map; + +import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.DEVICE_KEY_PREFIX; + +/** + * 更新语音 + */ +@Slf4j +@Component +public class FuncType5UpdateVoice implements NewMqttMessageRule { + + @Override + public String getCommandType() { + return "HBY100_5"; + } + + @Override + public void execute(NewMqttRuleContext context) { + log.info("HBY100J更新语音,消息负载:{}", context.getPayloadDict()); + + try { + String redisKey = GlobalConstants.GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + + context.getDeviceImei() + ":updateVoice"; + + Map payloadDict = context.getPayloadDict(); + if (payloadDict != null) { + RedisUtils.setCacheObject(redisKey, JSONObject.toJSONString(payloadDict)); + } + } catch (Exception e) { + log.error("HBY100J更新语音失败", e); + } + } +} diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/hby100j/FuncType6VoicePlay.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/hby100j/FuncType6VoicePlay.java new file mode 100644 index 00000000..44088709 --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/hby100j/FuncType6VoicePlay.java @@ -0,0 +1,43 @@ +package com.fuyuanshen.global.mqtt.rule.hby100j; + +import com.alibaba.fastjson2.JSONObject; +import com.fuyuanshen.common.core.constant.GlobalConstants; +import com.fuyuanshen.common.redis.utils.RedisUtils; +import com.fuyuanshen.global.mqtt.base.NewMqttMessageRule; +import com.fuyuanshen.global.mqtt.base.NewMqttRuleContext; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.Map; + +import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.DEVICE_KEY_PREFIX; + +/** + * 语音播报开启/关闭 + */ +@Slf4j +@Component +public class FuncType6VoicePlay implements NewMqttMessageRule { + + @Override + public String getCommandType() { + return "HBY100_6"; + } + + @Override + public void execute(NewMqttRuleContext context) { + log.info("HBY100J语音播报开启/关闭,消息负载:{}", context.getPayloadDict()); + + try { + String redisKey = GlobalConstants.GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + + context.getDeviceImei() + ":voicePlay"; + + Map payloadDict = context.getPayloadDict(); + if (payloadDict != null) { + RedisUtils.setCacheObject(redisKey, JSONObject.toJSONString(payloadDict)); + } + } catch (Exception e) { + log.error("HBY100J语音播报开启/关闭失败", e); + } + } +} diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/hby100j/FuncType9UpdateVolume.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/hby100j/FuncType9UpdateVolume.java new file mode 100644 index 00000000..79d9a6cb --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/hby100j/FuncType9UpdateVolume.java @@ -0,0 +1,43 @@ +package com.fuyuanshen.global.mqtt.rule.hby100j; + +import com.alibaba.fastjson2.JSONObject; +import com.fuyuanshen.common.core.constant.GlobalConstants; +import com.fuyuanshen.common.redis.utils.RedisUtils; +import com.fuyuanshen.global.mqtt.base.NewMqttMessageRule; +import com.fuyuanshen.global.mqtt.base.NewMqttRuleContext; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.Map; + +import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.DEVICE_KEY_PREFIX; + +/** + * 修改音量 + */ +@Slf4j +@Component +public class FuncType9UpdateVolume implements NewMqttMessageRule { + + @Override + public String getCommandType() { + return "HBY100_9"; + } + + @Override + public void execute(NewMqttRuleContext context) { + log.info("HBY100J修改音量,消息负载:{}", context.getPayloadDict()); + + try { + String redisKey = GlobalConstants.GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + + context.getDeviceImei() + ":updateVolume"; + + Map payloadDict = context.getPayloadDict(); + if (payloadDict != null) { + RedisUtils.setCacheObject(redisKey, JSONObject.toJSONString(payloadDict)); + } + } catch (Exception e) { + log.error("HBY100J修改音量失败", e); + } + } +} diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/hby100j/domin/FuncType10StrobeModeRequest.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/hby100j/domin/FuncType10StrobeModeRequest.java new file mode 100644 index 00000000..86d48511 --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/hby100j/domin/FuncType10StrobeModeRequest.java @@ -0,0 +1,58 @@ +package com.fuyuanshen.global.mqtt.rule.hby100j.domin; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * 爆闪模式开启/关闭 + * 对应 funcType="10" + * mode: + * 0 红色爆闪,1 蓝色爆闪,2 黄色爆闪,3,红色顺时针旋转爆闪,4黄色顺时针旋转爆闪,5,红蓝顺时针旋转爆闪,6 红蓝交替爆闪 + */ +@Data +public class FuncType10StrobeModeRequest { + + /** + * 请求唯一标识符(用于链路追踪或幂等处理) + */ + @JsonProperty("requestId") + private String requestId; + + /** + * 设备IMEI号(国际移动设备识别码) + */ + private String imei; + + /** + * 功能类型编码 + */ + private String funcType; + + /** + * 时间戳(毫秒级Unix时间戳) + */ + private Long timestamp; + + /** + * 语音配置数据 + */ + private Data data; + + /** + * 语音配置详情内部类 + */ + @lombok.Data + public static class Data { + + /** + * 爆闪模式是否启用 + * 0 - 禁用 + * 1 - 启用 + */ + private Integer enable; + /** + * 爆闪模式类型 0 红色爆闪,1 蓝色爆闪,2 黄色爆闪,3,红色顺时针旋转爆闪,4黄色顺时针旋转爆闪,5,红蓝顺时针旋转爆闪,6 红蓝交替爆闪 + **/ + private Integer mode; + } +} diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/hby100j/domin/FuncType11FrequencyRequest.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/hby100j/domin/FuncType11FrequencyRequest.java new file mode 100644 index 00000000..14ea3eed --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/hby100j/domin/FuncType11FrequencyRequest.java @@ -0,0 +1,47 @@ +package com.fuyuanshen.global.mqtt.rule.hby100j.domin; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * 修改警示灯爆闪频率 + * 对应 funcType="11" + */ +@Data +public class FuncType11FrequencyRequest { + + /** + * 请求唯一标识符(用于链路追踪或幂等处理) + */ + @JsonProperty("requestId") + private String requestId; + + /** + * 设备IMEI号(国际移动设备识别码) + */ + private String imei; + + /** + * 功能类型编码 + */ + private String funcType; + + /** + * 时间戳(毫秒级Unix时间戳) + */ + private Long timestamp; + + /** + * 语音配置数据 + */ + private Data data; + + /** + * 语音配置详情内部类 + */ + @lombok.Data + public static class Data { + //"frequency": 1-12 + private Integer frequency; + } +} diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/hby100j/domin/FuncType12ForceAudioRequest.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/hby100j/domin/FuncType12ForceAudioRequest.java new file mode 100644 index 00000000..6bd08597 --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/hby100j/domin/FuncType12ForceAudioRequest.java @@ -0,0 +1,52 @@ +package com.fuyuanshen.global.mqtt.rule.hby100j.domin; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * 强制声光报警开启/关闭 + * 对应 funcType="12" + */ +@Data +public class FuncType12ForceAudioRequest { + + /** + * 请求唯一标识符(用于链路追踪或幂等处理) + */ + @JsonProperty("requestId") + private String requestId; + + /** + * 设备IMEI号(国际移动设备识别码) + */ + private String imei; + + /** + * 功能类型编码 + */ + private String funcType; + + /** + * 时间戳(毫秒级Unix时间戳) + */ + private Long timestamp; + + /** + * 语音配置数据 + */ + private Data data; + + /** + * 语音配置详情内部类 + */ + @lombok.Data + public static class Data { + /** + * 语音报警0关闭,1开启 + */ + @JsonProperty("voice_strobe_alarm") + private Integer voiceStrobeAlarm; + //语音模式0公安,1消防,2应急,3交警,4市政,5铁路,6医疗,7部队,8水利 + private Integer mode; + } +} diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/hby100j/domin/FuncType13BrightnessRequest.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/hby100j/domin/FuncType13BrightnessRequest.java new file mode 100644 index 00000000..9fa6c6d5 --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/hby100j/domin/FuncType13BrightnessRequest.java @@ -0,0 +1,59 @@ +package com.fuyuanshen.global.mqtt.rule.hby100j.domin; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * 警示灯LED亮度调节 + * 对应 funcType="13" + */ +@Data +public class FuncType13BrightnessRequest { + + /** + * 请求唯一标识符(用于链路追踪或幂等处理) + */ + @JsonProperty("requestId") + private String requestId; + + /** + * 设备IMEI号(国际移动设备识别码) + */ + private String imei; + + /** + * 功能类型编码 + */ + private String funcType; + + /** + * 时间戳(毫秒级Unix时间戳) + */ + private Long timestamp; + + /** + * 语音配置数据 + */ + private Data data; + + /** + * 语音配置详情内部类 + */ + @lombok.Data + public static class Data { + /** + * 红色LED亮度值0-100 + */ + private Integer red; + + /** + * 蓝色LED亮度值0-100 + */ + private Integer blue; + + /** + * 黄色LED亮度值0-100 + */ + private Integer yellow; + } +} diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/hby100j/domin/FuncType14StatusReport.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/hby100j/domin/FuncType14StatusReport.java new file mode 100644 index 00000000..124af881 --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/hby100j/domin/FuncType14StatusReport.java @@ -0,0 +1,143 @@ +package com.fuyuanshen.global.mqtt.rule.hby100j.domin; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * 设备状态上报数据DTO + * 对应MQTT消息中设备上报的完整状态数据结构 + */ +@Data +public class FuncType14StatusReport { + + /** + * 设备IMEI号(国际移动设备识别码) + */ + private String imei; + + /** + * 功能类型编码(如"14"表示特定功能指令) + */ + private String funcType; + + /** + * 响应状态码(如"200"表示成功) + */ + private String status; + + /** + * 时间戳(毫秒级Unix时间戳) + */ + private Long timestamp; + + /** + * 设备详细状态数据 + */ + private Data data; + + /** + * 设备详细状态数据内部类 + */ + @lombok.Data + public static class Data { + + /** + * 语音播报开关:0-关闭,1-开启 + */ + @JsonProperty("voice_broadcast") + private Integer voiceBroadcast; + + /** + * 警报器(警笛)设置 + */ + @JsonProperty("siren_alarm") + private SirenAlarm sirenAlarm; + + /** + * LED爆闪设置 + */ + @JsonProperty("led_strobe") + private LedStrobe ledStrobe; + + /** + * 音量设置(0-100范围) + */ + @JsonProperty("volume") + private Integer volume; + + /** + * 亮度设置(RGB三色亮度值) + */ + @JsonProperty("brightness") + private Brightness brightness; + } + + /** + * 警报器(警笛)设置 + */ + @lombok.Data + public static class SirenAlarm { + + /** + * 是否启用:0-禁用,1-启用 + */ + @JsonProperty("enable") + private Integer enable; + + /** + * 工作模式:0-默认模式,其他值表示不同报警模式 + */ + @JsonProperty("mode") + private Integer mode; + } + + /** + * LED爆闪设置 + */ + @lombok.Data + public static class LedStrobe { + + /** + * 是否启用:0-禁用,1-启用 + */ + @JsonProperty("enable") + private Integer enable; + + /** + * 工作模式:0-默认模式,其他值表示不同爆闪模式 + */ + @JsonProperty("mode") + private Integer mode; + + /** + * 爆闪频率(单位:Hz或自定义单位) + */ + @JsonProperty("frequency") + private Integer frequency; + } + + /** + * 亮度设置(RGB三色) + */ + @lombok.Data + public static class Brightness { + + /** + * 红色通道亮度值(0-255或百分比) + */ + @JsonProperty("red") + private Integer red; + + /** + * 绿色通道亮度值(0-255或百分比) + */ + @JsonProperty("green") + private Integer green; + + /** + * 蓝色通道亮度值(0-255或百分比) + */ + @JsonProperty("blue") + private Integer blue; + } +} diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/hby100j/domin/FuncType5UpdateVoiceReport.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/hby100j/domin/FuncType5UpdateVoiceReport.java new file mode 100644 index 00000000..869da91f --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/hby100j/domin/FuncType5UpdateVoiceReport.java @@ -0,0 +1,59 @@ +package com.fuyuanshen.global.mqtt.rule.hby100j.domin; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * 更新设备语音 + */ +@Data +public class FuncType5UpdateVoiceReport { + + /** + * 设备IMEI号(国际移动设备识别码) + */ + private String imei; + + /** + * 功能类型编码("5" 表示操作状态上报) + */ + private String funcType; + + /** + * 响应状态码("200" 表示请求成功) + */ + private String status; + + /** + * 时间戳(毫秒级Unix时间戳) + */ + private Long timestamp; + + /** + * 操作详细状态数据 + */ + private Data data; + + /** + * 操作状态数据内部类 + */ + @lombok.Data + public static class Data { + + /** + * 操作执行状态: + * 0 - 未开始 / 失败 + * 1 - 执行中 + * 2 - 成功完成 + * (具体含义需结合业务协议定义) + */ + @JsonProperty("status") + private Integer status; + + /** + * 操作进度百分比(0-100),仅在 status=1(执行中)时有效 + */ + @JsonProperty("progress") + private Integer progress; + } +} diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/hby100j/domin/FuncType5UpdateVoiceRequest.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/hby100j/domin/FuncType5UpdateVoiceRequest.java new file mode 100644 index 00000000..58bc0cef --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/hby100j/domin/FuncType5UpdateVoiceRequest.java @@ -0,0 +1,62 @@ +package com.fuyuanshen.global.mqtt.rule.hby100j.domin; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * 语音资源配置请求DTO + * 对应 funcType="5" 的语音资源下发指令(如语音播报文件配置) + */ +@Data +public class FuncType5UpdateVoiceRequest { + + /** + * 请求唯一标识符(用于链路追踪或幂等处理) + */ + @JsonProperty("requestId") + private String requestId; + + /** + * 设备IMEI号(国际移动设备识别码) + */ + private String imei; + + /** + * 功能类型编码("5" 表示语音资源配置) + */ + private String funcType; + + /** + * 时间戳(毫秒级Unix时间戳) + */ + private Long timestamp; + + /** + * 语音配置数据 + */ + private Data data; + + /** + * 语音配置详情内部类 + */ + @lombok.Data + public static class Data { + + /** + * 语音类型: + * 0 - 默认语音(如标准提示音) + * 1 - 自定义语音 + * 2 - 紧急语音 + * (具体含义需依据设备协议定义) + */ + @JsonProperty("voice_type") + private Integer voiceType; + + /** + * 语音资源URL(MP3等音频文件地址) + * 示例:http://8.129.5.250:10001/voice/01.mp3 + */ + @JsonProperty("voice_resource") + private String voiceResource; + } +} diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/hby100j/domin/FuncType6VoicePlayRequest.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/hby100j/domin/FuncType6VoicePlayRequest.java new file mode 100644 index 00000000..d5bf3754 --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/hby100j/domin/FuncType6VoicePlayRequest.java @@ -0,0 +1,50 @@ +package com.fuyuanshen.global.mqtt.rule.hby100j.domin; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * 语音播报开启/关闭 + * 对应 funcType="6" + */ +@Data +public class FuncType6VoicePlayRequest { + + /** + * 请求唯一标识符(用于链路追踪或幂等处理) + */ + @JsonProperty("requestId") + private String requestId; + + /** + * 设备IMEI号(国际移动设备识别码) + */ + private String imei; + + /** + * 功能类型编码 + */ + private String funcType; + + /** + * 时间戳(毫秒级Unix时间戳) + */ + private Long timestamp; + + /** + * 语音配置数据 + */ + private Data data; + + /** + * 语音配置详情内部类 + */ + @lombok.Data + public static class Data { + /** + * 语音报警0关闭,1开启 + */ + @JsonProperty("voice_broadcast") + private Integer voiceBroadcast; + } +} diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/hby100j/domin/FuncType9UpdateVolumeRequest.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/hby100j/domin/FuncType9UpdateVolumeRequest.java new file mode 100644 index 00000000..e292fae1 --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/hby100j/domin/FuncType9UpdateVolumeRequest.java @@ -0,0 +1,47 @@ +package com.fuyuanshen.global.mqtt.rule.hby100j.domin; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * 修改音量 + * 对应 funcType="9" + */ +@Data +public class FuncType9UpdateVolumeRequest { + + /** + * 请求唯一标识符(用于链路追踪或幂等处理) + */ + @JsonProperty("requestId") + private String requestId; + + /** + * 设备IMEI号(国际移动设备识别码) + */ + private String imei; + + /** + * 功能类型编码 + */ + private String funcType; + + /** + * 时间戳(毫秒级Unix时间戳) + */ + private Long timestamp; + + /** + * 语音配置数据 + */ + private Data data; + + /** + * 语音配置详情内部类 + */ + @lombok.Data + public static class Data { + //"volume": 1-100(app端可根据需求把40作为低音量, 70作为中音量,100作为高音量) + private Integer volume; + } +} diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/hby100j/domin/FunctionType3LocationReport.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/hby100j/domin/FunctionType3LocationReport.java new file mode 100644 index 00000000..eea9e849 --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/hby100j/domin/FunctionType3LocationReport.java @@ -0,0 +1,56 @@ +package com.fuyuanshen.global.mqtt.rule.hby100j.domin; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * 设备位置上报DTO + * 对应MQTT消息中设备上报的地理位置数据 + */ +@Data +public class FunctionType3LocationReport { + + /** + * 设备IMEI号(国际移动设备识别码) + */ + private String imei; + + /** + * 功能类型编码(如"3"表示位置上报) + */ + private String funcType; + + /** + * 响应状态码(如"200"表示成功) + */ + private String status; + + /** + * 时间戳(毫秒级Unix时间戳) + */ + private Long timestamp; + + /** + * 位置详细数据 + */ + private Data data; + + /** + * 位置详细数据内部类 + */ + @lombok.Data + public static class Data { + + /** + * 经度(WGS-84坐标系,如22.543100) + */ + @JsonProperty("longitude") + private Double longitude; + + /** + * 纬度(WGS-84坐标系,如114.057900) + */ + @JsonProperty("latitude") + private Double latitude; + } +} diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/hby100j/domin/FunctionType4PowerStatusReport.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/hby100j/domin/FunctionType4PowerStatusReport.java new file mode 100644 index 00000000..b74cb2ff --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/hby100j/domin/FunctionType4PowerStatusReport.java @@ -0,0 +1,77 @@ +package com.fuyuanshen.global.mqtt.rule.hby100j.domin; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * 设备电源状态上报DTO + * 对应MQTT消息中设备上报的电源相关状态数据 + */ +@Data +public class FunctionType4PowerStatusReport { + + /** + * 设备IMEI号(国际移动设备识别码) + */ + private String imei; + + /** + * 功能类型编码(如"4"表示电源状态上报) + */ + private String funcType; + + /** + * 响应状态码(如"200"表示成功) + */ + private String status; + + /** + * 时间戳(毫秒级Unix时间戳) + */ + private Long timestamp; + + /** + * 电源详细状态数据 + */ + private Data data; + + /** + * 电源详细状态数据内部类 + */ + @lombok.Data + public static class Data { + + /** + * 电池容量(如"5000mAh") + */ + @JsonProperty("capacity") + private String capacity; + + /** + * 电压值(单位:V,0表示未检测或无电) + */ + @JsonProperty("voltage") + private Integer voltage; + + /** + * 电量百分比(0-100,0表示低电量或未检测) + */ + @JsonProperty("level") + private Integer level; + + /** + * 充电状态:0-未充电,1-正在充电 + */ + @JsonProperty("charge") + private Integer charge; + + /** + * 12V电源状态:0-关闭/无输出,1-开启/有输出 + */ + @JsonProperty("12v_power") + private Integer twelveVPower; + + @JsonProperty("battery_remaining_time") + private Integer batteryRemainingTime; + } +} diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/utils/FfmpegVolumeUtil.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/utils/FfmpegVolumeUtil.java new file mode 100644 index 00000000..2e7b5e65 --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/utils/FfmpegVolumeUtil.java @@ -0,0 +1,139 @@ +package com.fuyuanshen.global.mqtt.utils; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; + +public class FfmpegVolumeUtil { + + /** + * 使用FFmpeg增加MP3文件的音量 + * + * @param inputFilePath 输入MP3文件路径 + * @param outputFilePath 输出MP3文件路径 + * @param volumeGain 音量增益(例如:1.5表示增加50%音量,2.0表示翻倍) + */ + public static void increaseMp3Volume(String inputFilePath, String outputFilePath, int volumeGain) { + boolean ffmpegAvailable = isFfmpegAvailable(); + if (!ffmpegAvailable) { + System.err.println("FFmpeg未安装或未找到!请安装FFmpeg并确保在系统路径中。"); + return; + } + + // 检查输入文件 + File inputFile = new File(inputFilePath); + if (!inputFile.exists()) { + System.err.println("输入文件不存在:" + inputFilePath); + return; + } + if (!inputFile.canRead()) { + System.err.println("输入文件不可读:" + inputFilePath); + return; + } + + // 创建输出目录 + File outputFile = new File(outputFilePath); + File parentDir = outputFile.getParentFile(); + if (parentDir != null) { + parentDir.mkdirs(); + if (!parentDir.canWrite()) { + System.err.println("输出目录不可写:" + parentDir.getAbsolutePath()); + return; + } + } + + Process process = null; + try { + String command = String.format( + "ffmpeg -y -i \"%s\" -af \"volume=%sdB\" \"%s\"", + inputFilePath, + volumeGain, + outputFilePath + ); + + System.out.println("执行命令: " + command); + + // 使用 ProcessBuilder 提供更好的控制 + ProcessBuilder processBuilder = new ProcessBuilder("ffmpeg", "-y", + "-i", inputFilePath, "-af", "volume=" + volumeGain + "dB", outputFilePath); + processBuilder.redirectErrorStream(false); + process = processBuilder.start(); + + // 同时读取标准输出和错误输出 + Process finalProcess = process; + Thread stdOutThread = new Thread(() -> { + try (BufferedReader reader = new BufferedReader( + new InputStreamReader(finalProcess.getInputStream()))) { + String line; + while ((line = reader.readLine()) != null) { + System.out.println("[FFmpeg STDOUT] " + line); + } + } catch (IOException e) { + System.err.println("读取标准输出时出错: " + e.getMessage()); + }finally { + finalProcess.destroy(); + } + }); + + Process finalProcess1 = process; + Thread stdErrThread = new Thread(() -> { + try (BufferedReader reader = new BufferedReader( + new InputStreamReader(finalProcess1.getErrorStream()))) { + String line; + while ((line = reader.readLine()) != null) { + System.err.println("[FFmpeg STDERR] " + line); + } + } catch (IOException e) { + System.err.println("读取错误输出时出错: " + e.getMessage()); + }finally { + finalProcess1.destroy(); + } + }); + + stdOutThread.start(); + stdErrThread.start(); + + int exitCode = process.waitFor(); + stdOutThread.join(); + stdErrThread.join(); + + if (exitCode == 0) { + System.out.println("音量调整成功!输出文件:" + outputFilePath); + } else { + System.err.println("FFmpeg命令执行失败,退出码:" + exitCode); + } + } catch (IOException | InterruptedException e) { + System.err.println("执行FFmpeg命令时发生错误: " + e.getMessage()); + e.printStackTrace(); + } finally { + if (process != null) { + System.out.println("已销毁进程"); + process.destroy(); + } + } + } + + + public static void main(String[] args) { + + // 示例用法 + String inputPath = "/app/input.mp3"; + String outputPath = "/app/output17.mp3"; + int volumeGain = 12; + + increaseMp3Volume(inputPath, outputPath, volumeGain); + } + + private static boolean isFfmpegAvailable() { + try { + Process process = Runtime.getRuntime().exec("ffmpeg -version"); + int exitCode = process.waitFor(); + return exitCode == 0; + } catch (Exception e) { + return false; + } + } + +} + diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/utils/FfmpegVolumeUtil3.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/utils/FfmpegVolumeUtil3.java new file mode 100644 index 00000000..1ef0126d --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/utils/FfmpegVolumeUtil3.java @@ -0,0 +1,92 @@ +package com.fuyuanshen.global.mqtt.utils; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; + +public class FfmpegVolumeUtil3 { + + /** + * 使用FFmpeg增加MP3文件的音量 + * + * @param inputFilePath 输入MP3文件路径 + * @param outputFilePath 输出MP3文件路径 + * @param volumeGain 音量增益(例如:1.5表示增加50%音量,2.0表示翻倍) + */ + public static void increaseMp3Volume(String inputFilePath, String outputFilePath, int volumeGain) { + boolean ffmpegAvailable = isFfmpegAvailable(); + if (!ffmpegAvailable) { + System.err.println("FFmpeg未安装或未找到!请安装FFmpeg并确保在系统路径中。"); + return; + } + // 创建输出文件的父目录 + File outputFile = new File(outputFilePath); + File parentDir = outputFile.getParentFile(); + if (parentDir != null && !parentDir.exists()) { + boolean created = parentDir.mkdirs(); + if (!created) { + System.err.println("无法创建输出目录:" + parentDir.getAbsolutePath()); + return; + } + } + Process process = null; + try { + // 构建FFmpeg命令 + String command = String.format( + "ffmpeg -y -i \"%s\" -af \"volume=%sdB\" \"%s\"", + inputFilePath, + volumeGain, + outputFilePath + ); + + System.out.println("执行命令: " + command); + + // 执行FFmpeg命令 + process = Runtime.getRuntime().exec(command); + + // 读取命令执行结果 + BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); + String line; + while ((line = reader.readLine()) != null) { + System.out.println(line); + } + + // 等待命令执行完成 + int exitCode = process.waitFor(); + if (exitCode == 0) { + System.out.println("音量调整成功!输出文件:" + outputFilePath); + } else { + System.err.println("FFmpeg命令执行失败,退出码:" + exitCode); + } + } catch (IOException | InterruptedException e) { + e.printStackTrace(); + } finally { + if (process != null) { + process.destroy(); + } + } + } + + public static void main(String[] args) { + + // 示例用法 + String inputPath = "/app/input.mp3"; + String outputPath = "/app/output18.mp3"; + int volumeGain = 12; + + increaseMp3Volume(inputPath, outputPath, volumeGain); + } + + private static boolean isFfmpegAvailable() { + try { + Process process = Runtime.getRuntime().exec("ffmpeg -version"); + int exitCode = process.waitFor(); + return exitCode == 0; + } catch (Exception e) { + return false; + } + } + +} + diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/utils/GenerateIdUtil.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/utils/GenerateIdUtil.java new file mode 100644 index 00000000..6c9d10c3 --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/utils/GenerateIdUtil.java @@ -0,0 +1,24 @@ +package com.fuyuanshen.global.mqtt.utils; + +import java.util.Random; +import java.util.UUID; + +public class GenerateIdUtil { + /** + * 生成数字唯一ID - 基于UUID + */ + public static String generateNumericId() { + UUID uuid = UUID.randomUUID(); + return Math.abs(uuid.toString().hashCode()) + ""; + } + + /** + * 生成数字唯一ID - 基于时间戳+随机数 + */ + public static long generateTimestampBasedId() { + long timestamp = System.currentTimeMillis(); + int random = new Random().nextInt(1000); // 0-999随机数 + return timestamp * 1000 + random; + } + +} diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/queue/MqttMessageConsumer.java b/fys-admin/src/main/java/com/fuyuanshen/global/queue/MqttMessageConsumer.java index 0ac95099..de5e16db 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/global/queue/MqttMessageConsumer.java +++ b/fys-admin/src/main/java/com/fuyuanshen/global/queue/MqttMessageConsumer.java @@ -7,14 +7,18 @@ import com.fuyuanshen.common.core.utils.StringUtils; import com.fuyuanshen.common.redis.utils.RedisUtils; import com.fuyuanshen.equipment.domain.Device; import com.fuyuanshen.equipment.mapper.DeviceMapper; +import com.fuyuanshen.global.mqtt.base.NewMqttRuleContext; import com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants; import jakarta.annotation.PostConstruct; import jakarta.annotation.PreDestroy; import lombok.extern.slf4j.Slf4j; +import org.redisson.api.RLock; +import org.redisson.api.RedissonClient; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import java.time.Duration; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; @@ -95,40 +99,18 @@ public class MqttMessageConsumer { } // 处理具体业务逻辑的方法 - private void processMessage(String message) { + private void processMessage(String imei) { String threadName = Thread.currentThread().getName(); try { - log.info("业务处理线程 {} 开始处理消息: {}", threadName, message); -// String deviceOnlineStatusRedisKey = GlobalConstants.GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX+ message + DeviceRedisKeyConstants.DEVICE_ONLINE_STATUS_KEY_PREFIX ; -// String deviceOnlineStatusRedis = RedisUtils.getCacheObject(deviceOnlineStatusRedisKey); -// if(StringUtils.isBlank(deviceOnlineStatusRedis)){ -// UpdateWrapper updateWrapper = new UpdateWrapper<>(); -// updateWrapper.eq("device_imei", message) -// .set("online_status", 1); -// deviceMapper.update(updateWrapper); -// } -// QueryWrapper queryWrapper = new QueryWrapper<>(); -// queryWrapper.eq("device_imei", message); -// queryWrapper.eq("online_status", 1); -// Long count = deviceMapper.selectCount(queryWrapper); -// if(count == 0){ -// UpdateWrapper updateWrapper = new UpdateWrapper<>(); -// updateWrapper.eq("device_imei", message) -// .eq("online_status", 0) -// .set("online_status", 1); -// deviceMapper.update(updateWrapper); -// } + // 执行业务逻辑 UpdateWrapper updateWrapper = new UpdateWrapper<>(); - updateWrapper.eq("device_imei", message) + updateWrapper.eq("device_imei", imei) .in("online_status", 0,2) .set("online_status", 1); int update = deviceMapper.update(updateWrapper); - // 模拟业务处理耗时 -// Thread.sleep(200); - - log.info("业务处理线程 {} 完成消息处理: {}", threadName, message); + } catch (Exception e) { - log.error("业务处理线程 {} 处理消息时发生错误: {}", threadName, message, e); + log.error("业务处理线程 {} 处理消息时发生错误: {}", threadName, imei, e); } } diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceDebugController.java b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceDebugController.java index fa204c02..e05a89c4 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceDebugController.java +++ b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceDebugController.java @@ -1,19 +1,15 @@ package com.fuyuanshen.web.controller.device; -import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; -import com.fuyuanshen.app.domain.bo.AppOperationVideoBo; -import com.fuyuanshen.app.domain.dto.AppDeviceLogoUploadDto; +import com.fuyuanshen.equipment.domain.bo.AppOperationVideoBo; import com.fuyuanshen.app.domain.dto.AppFileDto; import com.fuyuanshen.common.core.domain.R; import com.fuyuanshen.common.core.exception.ServiceException; import com.fuyuanshen.common.log.annotation.Log; -import com.fuyuanshen.common.log.enums.BusinessType; import com.fuyuanshen.common.mybatis.core.page.PageQuery; import com.fuyuanshen.common.mybatis.core.page.TableDataInfo; import com.fuyuanshen.common.ratelimiter.annotation.FunctionAccessAnnotation; import com.fuyuanshen.common.web.core.BaseController; import com.fuyuanshen.equipment.domain.query.DeviceQueryCriteria; -import com.fuyuanshen.equipment.domain.vo.AppDeviceVo; import com.fuyuanshen.equipment.domain.vo.WebDeviceVo; import com.fuyuanshen.web.domain.Dto.DeviceDebugEditDto; import com.fuyuanshen.web.domain.Dto.DeviceDebugLogoUploadDto; @@ -28,8 +24,6 @@ import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; -import java.util.ArrayList; -import java.util.List; /** * 联调中心 @@ -125,7 +119,7 @@ public class DeviceDebugController extends BaseController { } deviceDebugService.delFile(bo.getFileIds()); // 修改操作视频 - if (bo.getVideoUrl().isEmpty()) { + if (!bo.getVideoUrl().isEmpty()) { AppOperationVideoBo appOperationVideoBo = new AppOperationVideoBo(); appOperationVideoBo.setDeviceIds(new Long[]{ bo.getDeviceId() }); appOperationVideoBo.setVideoUrl(bo.getVideoUrl()); diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/WebVideoController.java b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/WebVideoController.java new file mode 100644 index 00000000..75eaeb82 --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/WebVideoController.java @@ -0,0 +1,137 @@ +package com.fuyuanshen.web.controller.device; + +import com.fuyuanshen.app.domain.dto.AppAudioFileDto; +import com.fuyuanshen.app.domain.dto.AppFileRenameDto; +import com.fuyuanshen.app.domain.dto.TextToSpeechRequest; +import com.fuyuanshen.app.service.AudioProcessService; +import com.fuyuanshen.app.service.VideoProcessService; +import com.fuyuanshen.common.core.domain.R; +import com.fuyuanshen.common.idempotent.annotation.RepeatSubmit; +import com.fuyuanshen.common.web.core.BaseController; +import com.fuyuanshen.equipment.domain.vo.AppFileVo; +import lombok.RequiredArgsConstructor; +import org.springframework.http.MediaType; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** + * APP 视频处理控制器 + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/api/video") +public class WebVideoController extends BaseController { + + private final VideoProcessService videoProcessService; + private final AudioProcessService audioProcessService; + + /** + * 上传视频转码code默认1:RGB565 2:BGR565 + */ + @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + @RepeatSubmit(interval = 2, timeUnit = TimeUnit.SECONDS,message = "请勿重复提交!") + public R> uploadVideo(@RequestParam("file") MultipartFile file, @RequestParam(defaultValue = "1") int code) { + return R.ok(videoProcessService.processVideo(file, code)); + } + + /** + * 上传音频文件并转码 + */ + @PostMapping(value = "/audio", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + @RepeatSubmit(interval = 2, timeUnit = TimeUnit.SECONDS,message = "请勿重复提交!") + public R> uploadAudio(@RequestParam("file") MultipartFile file) { + return R.ok(audioProcessService.processAudio(file)); + } + + /** + * 文字转音频TTS服务 + */ + @GetMapping("/audioTTS") + @RepeatSubmit(interval = 2, timeUnit = TimeUnit.SECONDS,message = "请勿重复提交!") + public R> uploadAudioTTS(@RequestParam String text) throws IOException { + return R.ok(audioProcessService.generateStandardPcmData(text)); + } + + + /** + * 提取文本内容(只支持txt/docx) + */ + @PostMapping(value = "/extract", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + @RepeatSubmit(interval = 2, timeUnit = TimeUnit.SECONDS,message = "请勿重复提交!") + public R extract(@RequestParam("file") MultipartFile file) throws Exception { + return R.ok("Success",audioProcessService.extract(file)); + } + + + /** + * 上传音频文件进行处理 + * 支持MP3、WAV、PCM格式 + */ + @PostMapping("/uploadAudioToOss") + public R uploadAudioToOss(@ModelAttribute AppAudioFileDto bo) { + try { + bo.setSource("web"); + String result = audioProcessService.uploadAudioToOss(bo); + return R.ok(result); + } catch (IllegalArgumentException e) { + return R.fail("文件格式错误: " + e.getMessage()); + } catch (Exception e) { + + return R.fail("上传处理失败: " + e.getMessage()); + } + } + + /** + * 文本转语音 + * 支持MP3、WAV、PCM格式 + */ + @PostMapping("/ttsToOss") + public R textToSpeech(@RequestBody TextToSpeechRequest request) { + try { + if (request.getDeviceId() == null) { + return R.fail("设备ID不能为空"); + } + + String result = audioProcessService.textToSpeech( + request.getDeviceId(), + request.getText(), + request.getFileSuffix(), + "web" + ); + return R.ok(result); + } catch (Exception e) { + return R.fail("文本转语音失败: " + e.getMessage()); + } + } + + + /** + * 查询语音文件列表 + */ + @GetMapping("/queryAudioFileList") + public R> queryAudioFileList(Long deviceId) { + return R.ok(audioProcessService.queryAudioFileList(deviceId,"web")); + } + + /** + * 删除语音文件 + */ + @GetMapping("/deleteAudioFile") + public R deleteAudioFile(Long id) { + return audioProcessService.deleteAudioFile(id); + } + + /** + * 文件重命名 + */ + @PostMapping("/renameAudioFile") + public R renameAudioFile(@RequestBody AppFileRenameDto bo) { + return audioProcessService.renameAudioFile(bo); + } +} diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/bjq/WebDeviceHBY100JController.java b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/bjq/WebDeviceHBY100JController.java new file mode 100644 index 00000000..c95d10a7 --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/bjq/WebDeviceHBY100JController.java @@ -0,0 +1,107 @@ +package com.fuyuanshen.web.controller.device.bjq; + +import com.fuyuanshen.app.domain.vo.AppDeviceHBY100JDetailVo; +import com.fuyuanshen.common.core.domain.R; +import com.fuyuanshen.common.web.core.BaseController; +import com.fuyuanshen.web.controller.device.domain.dto.*; +import com.fuyuanshen.web.service.device.DeviceHBY100JBizService; +import jakarta.validation.constraints.NotNull; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +/** + * HBY100J设备控制类 + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/api/hby100j/device") +public class WebDeviceHBY100JController extends BaseController { + + private final DeviceHBY100JBizService deviceHBY100JBizService; + + + /** + * 获取设备详细信息 + * + * @param id 主键 + */ + @GetMapping("/{id}") + public R getInfo(@NotNull(message = "主键不能为空") + @PathVariable Long id) { + return R.ok(deviceHBY100JBizService.getInfo(id)); + } + + /** + * 更新语音 + */ + @PostMapping("/updateVoice") + public R updateVoice(@RequestBody HBY100JUpdateVoiceDto dto) { + dto.setCommunicationMode(0); + deviceHBY100JBizService.updateVoice(dto); + return R.ok(); + } + + /** + * 强制报警 + * + */ + + @PostMapping("/forceAlarmActivation") + public R forceAlarmActivation(@RequestBody HBY100JForceAlarmActivationDto bo) { + deviceHBY100JBizService.forceAlarmActivation(bo); + return R.ok(); + } + + /** + * 语音播报 + * + */ + @PostMapping("/voiceBroadcast") + public R voiceBroadcast(@RequestBody HBY100JVoiceBroadcastDto params) { + deviceHBY100JBizService.voiceBroadcast(params); + return R.ok(); + } + + + + + /** + * 爆闪模式 + */ + @PostMapping("/strobeMode") + public R strobeMode(@RequestBody HBY100JStrobeModeDto params) { + deviceHBY100JBizService.strobeMode(params); + return R.ok(); + } + + + /** + * 灯光调节 + */ + @PostMapping("/lightAdjustment") + public R lightAdjustment(@RequestBody HBY100JLightAdjustmentDto params) { + deviceHBY100JBizService.lightAdjustment(params); + return R.ok(); + } + + + /** + * 爆闪频率 + */ + @PostMapping("/strobeFrequency") + public R strobeFrequency(@RequestBody HBY100JStrobeFrequencyDto params) { + deviceHBY100JBizService.strobeFrequency(params); + return R.ok(); + } + + /** + * 修改音量 + */ + @PostMapping("/updateVolume") + public R updateVolume(@RequestBody HBY100JUpdateVolumeDto params) { + deviceHBY100JBizService.updateVolume(params); + return R.ok(); + } +} diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/domain/dto/HBY100JForceAlarmActivationDto.java b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/domain/dto/HBY100JForceAlarmActivationDto.java new file mode 100644 index 00000000..2ac0e282 --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/domain/dto/HBY100JForceAlarmActivationDto.java @@ -0,0 +1,23 @@ +package com.fuyuanshen.web.controller.device.domain.dto; + +import lombok.Data; + +import java.util.List; + +@Data +public class HBY100JForceAlarmActivationDto { + + /** + * 设备ID + */ + List deviceIds; + /** + * 0 关闭, 1开启 + */ + private Integer voiceStrobeAlarm; + /** + * 0 公安,1消防,2应急,3交警,4 市政,5 铁路,6 医疗,7语音 + */ + private Integer mode; + +} \ No newline at end of file diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/domain/dto/HBY100JLightAdjustmentDto.java b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/domain/dto/HBY100JLightAdjustmentDto.java new file mode 100644 index 00000000..0487309e --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/domain/dto/HBY100JLightAdjustmentDto.java @@ -0,0 +1,31 @@ +package com.fuyuanshen.web.controller.device.domain.dto; + +import lombok.Data; + +@Data +public class HBY100JLightAdjustmentDto{ + /** + * 设备ID + */ + private Long deviceId; + + /** + * 亮度值0-100 + */ + private Integer brightness; +// /** +// * 红色LED亮度值0-100 +// */ +// private Integer red; +// +// /** +// * 蓝色LED亮度值0-100 +// */ +// private Integer blue; +// +// /** +// * 黄色LED亮度值0-100 +// */ +// private Integer yellow; + +} \ No newline at end of file diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/domain/dto/HBY100JStrobeFrequencyDto.java b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/domain/dto/HBY100JStrobeFrequencyDto.java new file mode 100644 index 00000000..714bc34b --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/domain/dto/HBY100JStrobeFrequencyDto.java @@ -0,0 +1,16 @@ +package com.fuyuanshen.web.controller.device.domain.dto; + +import lombok.Data; + +@Data +public class HBY100JStrobeFrequencyDto{ + /** + * 设备ID + */ + private Long deviceId; + /** + * "frequency": 1-12 + */ + private Integer frequency; + +} \ No newline at end of file diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/domain/dto/HBY100JStrobeModeDto.java b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/domain/dto/HBY100JStrobeModeDto.java new file mode 100644 index 00000000..568e5261 --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/domain/dto/HBY100JStrobeModeDto.java @@ -0,0 +1,21 @@ +package com.fuyuanshen.web.controller.device.domain.dto; + +import lombok.Data; + +@Data +public class HBY100JStrobeModeDto{ + + /** + * 设备ID + */ + private Long deviceId; + /** + * 0 关闭 1 开启 + */ + private Integer enable; + + /** + * 0 红色爆闪,1 蓝色爆闪,2 黄色爆闪,3,红色顺时针旋转爆闪,4黄色顺时针旋转爆闪,5,红蓝顺时针旋转爆闪,6 红蓝交替爆闪 + */ + private Integer mode; +} \ No newline at end of file diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/domain/dto/HBY100JUpdateVoiceDto.java b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/domain/dto/HBY100JUpdateVoiceDto.java new file mode 100644 index 00000000..c7374143 --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/domain/dto/HBY100JUpdateVoiceDto.java @@ -0,0 +1,14 @@ +package com.fuyuanshen.web.controller.device.domain.dto; + +import lombok.Data; + +@Data +public class HBY100JUpdateVoiceDto { + + private Long id; + + /** + * 通讯方式 0:4G;1:蓝牙 + */ + private Integer communicationMode; +} \ No newline at end of file diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/domain/dto/HBY100JUpdateVolumeDto.java b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/domain/dto/HBY100JUpdateVolumeDto.java new file mode 100644 index 00000000..59a704bc --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/domain/dto/HBY100JUpdateVolumeDto.java @@ -0,0 +1,16 @@ +package com.fuyuanshen.web.controller.device.domain.dto; + +import lombok.Data; + +@Data +public class HBY100JUpdateVolumeDto{ + /** + * 设备ID + */ + private Long deviceId; + /** + * "volume": 1-100(app端可根据需求把40作为低音量, 70作为中音量,100作为高音量) + */ + private Integer volume; + +} \ No newline at end of file diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/domain/dto/HBY100JVoiceBroadcastDto.java b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/domain/dto/HBY100JVoiceBroadcastDto.java new file mode 100644 index 00000000..a18b013f --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/web/controller/device/domain/dto/HBY100JVoiceBroadcastDto.java @@ -0,0 +1,17 @@ +package com.fuyuanshen.web.controller.device.domain.dto; + +import lombok.Data; + +@Data +public class HBY100JVoiceBroadcastDto { + + /** + * 设备ID + */ + Long deviceId; + /** + * 0 关闭, 1开启 + */ + private Integer voiceBroadcast; + +} \ No newline at end of file diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/domain/vo/DeviceInfoVo.java b/fys-admin/src/main/java/com/fuyuanshen/web/domain/vo/DeviceInfoVo.java index e0c637b5..80df8a64 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/web/domain/vo/DeviceInfoVo.java +++ b/fys-admin/src/main/java/com/fuyuanshen/web/domain/vo/DeviceInfoVo.java @@ -1,10 +1,7 @@ package com.fuyuanshen.web.domain.vo; -import com.fuyuanshen.app.domain.vo.AppBusinessFileVo; -import com.fuyuanshen.app.domain.vo.AppFileVo; -import com.fuyuanshen.app.domain.vo.AppOperationVideoVo; -import com.fuyuanshen.equipment.domain.Device; -import com.fuyuanshen.equipment.domain.vo.AppDeviceVo; +import com.fuyuanshen.equipment.domain.vo.AppFileVo; +import com.fuyuanshen.equipment.domain.vo.AppOperationVideoVo; import lombok.Data; import java.util.List; diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceBizService.java b/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceBizService.java index 09941325..fbaf2ce7 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceBizService.java +++ b/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceBizService.java @@ -86,7 +86,7 @@ public class DeviceBizService { List records = result.getRecords(); if (records != null && !records.isEmpty()) { records.forEach(item -> { - if (item.getCommunicationMode() != null && item.getCommunicationMode() == 0) { + if (item.getCommunicationMode() != null && (item.getCommunicationMode() == 0 || item.getCommunicationMode() == 2)) { // 设备在线状态 String onlineStatus = RedisUtils.getCacheObject(GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + item.getDeviceImei() + DeviceRedisKeyConstants.DEVICE_ONLINE_STATUS_KEY_PREFIX); @@ -97,6 +97,7 @@ public class DeviceBizService { } else { item.setOnlineStatus(0); } + String deviceStatus = RedisUtils.getCacheObject(GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + item.getDeviceImei() + DEVICE_STATUS_KEY_PREFIX); // 获取电量 if (StringUtils.isNotBlank(deviceStatus)) { @@ -270,12 +271,12 @@ public class DeviceBizService { if (type == 0) { QueryWrapper bindRecordQueryWrapper = new QueryWrapper<>(); bindRecordQueryWrapper.eq("device_id", device.getId()); - bindRecordQueryWrapper.eq("binding_user_id", userId); +// bindRecordQueryWrapper.eq("binding_user_id", userId); Long count = appDeviceBindRecordMapper.selectCount(bindRecordQueryWrapper); if (count == 0) { throw new RuntimeException("请先绑定设备!!!"); } - if (count < 2) { + if (count <= 1) { UpdateWrapper deviceUpdateWrapper = new UpdateWrapper<>(); deviceUpdateWrapper.eq("id", device.getId()) .set("binding_user_id", null) diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceDebugService.java b/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceDebugService.java index afea4858..078c3962 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceDebugService.java +++ b/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceDebugService.java @@ -1,13 +1,13 @@ package com.fuyuanshen.web.service.device; import cn.hutool.core.collection.CollUtil; -import com.fuyuanshen.app.domain.AppBusinessFile; -import com.fuyuanshen.app.domain.AppOperationVideo; -import com.fuyuanshen.app.domain.bo.AppBusinessFileBo; -import com.fuyuanshen.app.domain.bo.AppOperationVideoBo; +import com.fuyuanshen.equipment.domain.AppBusinessFile; +import com.fuyuanshen.equipment.domain.AppOperationVideo; +import com.fuyuanshen.equipment.domain.bo.AppBusinessFileBo; +import com.fuyuanshen.equipment.domain.bo.AppOperationVideoBo; import com.fuyuanshen.app.domain.dto.AppFileDto; -import com.fuyuanshen.app.service.IAppBusinessFileService; -import com.fuyuanshen.app.service.IAppOperationVideoService; +import com.fuyuanshen.equipment.service.IAppBusinessFileService; +import com.fuyuanshen.equipment.service.IAppOperationVideoService; import com.fuyuanshen.common.core.exception.ServiceException; import com.fuyuanshen.common.satoken.utils.AppLoginHelper; import com.fuyuanshen.equipment.service.DeviceService; diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceHBY100JBizService.java b/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceHBY100JBizService.java new file mode 100644 index 00000000..b46c73b6 --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceHBY100JBizService.java @@ -0,0 +1,441 @@ +package com.fuyuanshen.web.service.device; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; +import com.fuyuanshen.app.domain.vo.AppDeviceHBY100JDetailVo; +import com.fuyuanshen.app.mapper.AppDeviceShareMapper; +import com.fuyuanshen.common.core.constant.GlobalConstants; +import com.fuyuanshen.common.core.domain.model.AppLoginUser; +import com.fuyuanshen.common.core.domain.model.LoginUser; +import com.fuyuanshen.common.core.exception.ServiceException; +import com.fuyuanshen.common.core.utils.StringUtils; +import com.fuyuanshen.common.redis.utils.RedisUtils; +import com.fuyuanshen.common.satoken.utils.AppLoginHelper; +import com.fuyuanshen.common.satoken.utils.LoginHelper; +import com.fuyuanshen.equipment.domain.AppBusinessFile; +import com.fuyuanshen.equipment.domain.Device; +import com.fuyuanshen.equipment.domain.DeviceType; +import com.fuyuanshen.equipment.domain.vo.AppBusinessFileVo; +import com.fuyuanshen.equipment.mapper.AppBusinessFileMapper; +import com.fuyuanshen.equipment.mapper.DeviceLogMapper; +import com.fuyuanshen.equipment.mapper.DeviceMapper; +import com.fuyuanshen.equipment.mapper.DeviceTypeMapper; +import com.fuyuanshen.equipment.service.IAppBusinessFileService; +import com.fuyuanshen.global.mqtt.config.MqttGateway; +import com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants; +import com.fuyuanshen.global.mqtt.constants.MqttConstants; +import com.fuyuanshen.global.mqtt.rule.hby100j.domin.*; +import com.fuyuanshen.global.mqtt.utils.GenerateIdUtil; +import com.fuyuanshen.system.domain.vo.SysOssVo; +import com.fuyuanshen.system.mapper.SysOssMapper; +import com.fuyuanshen.web.controller.device.domain.dto.*; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.Date; +import java.util.List; + +import static com.fuyuanshen.common.core.constant.GlobalConstants.GLOBAL_REDIS_KEY; +import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.DEVICE_KEY_PREFIX; +import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.DEVICE_LOCATION_KEY_PREFIX; + + +@Slf4j +@Service +@RequiredArgsConstructor +public class DeviceHBY100JBizService { + + private final DeviceMapper deviceMapper; + private final DeviceTypeMapper deviceTypeMapper; + private final MqttGateway mqttGateway; + private final DeviceLogMapper deviceLogMapper; + private final IAppBusinessFileService appBusinessFileService; + private final AppBusinessFileMapper appBusinessFileMapper; + private final SysOssMapper sysOssMapper; + private final AppDeviceShareMapper appDeviceShareMapper; + + + private static final String DEVICE_TYPE = "HBY100/"; + + private static final Long pcUserId = 0L; + /** + * 记录设备操作日志 + * + * @param deviceId 设备ID + * @param content 日志内容 + * @param operator 操作人 + */ + private void recordDeviceLog(Long deviceId, String deviceName, String deviceAction, String content, Long operator) { + try { + // 创建设备日志实体 + com.fuyuanshen.equipment.domain.DeviceLog deviceLog = new com.fuyuanshen.equipment.domain.DeviceLog(); + deviceLog.setDeviceId(deviceId); + deviceLog.setDeviceAction(deviceAction); + deviceLog.setContent(content); + deviceLog.setCreateBy(operator); + deviceLog.setDeviceName(deviceName); + deviceLog.setCreateTime(new Date()); + + // 插入日志记录 + deviceLogMapper.insert(deviceLog); + } catch (Exception e) { + log.error("记录设备操作日志失败: {}", e.getMessage(), e); + } + } + + + public AppDeviceHBY100JDetailVo getInfo(Long id) { + Device device = deviceMapper.selectById(id); + if (device == null) { + throw new RuntimeException("请先将设备入库!!!"); + } + + AppDeviceHBY100JDetailVo vo = new AppDeviceHBY100JDetailVo(); + vo.setDeviceId(device.getId()); + vo.setDeviceName(device.getDeviceName()); + vo.setDevicePic(device.getDevicePic()); + vo.setDeviceImei(device.getDeviceImei()); + vo.setDeviceMac(device.getDeviceMac()); + DeviceType deviceType = deviceTypeMapper.selectById(device.getDeviceType()); + if (deviceType != null) { + vo.setCommunicationMode(Integer.valueOf(deviceType.getCommunicationMode())); + vo.setTypeName(deviceType.getTypeName()); + } + vo.setBluetoothName(device.getBluetoothName()); + + // 设备在线状态 + String onlineStatus = RedisUtils.getCacheObject(GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + device.getDeviceImei() + DeviceRedisKeyConstants.DEVICE_ONLINE_STATUS_KEY_PREFIX); + // 设备在线状态 + if ("1".equals(onlineStatus)) { + vo.setOnlineStatus(1); + } else if ("2".equals(onlineStatus)) { + vo.setOnlineStatus(2); + } else { + vo.setOnlineStatus(0); + } + + String deviceStatus = RedisUtils.getCacheObject(GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + device.getDeviceImei() + DeviceRedisKeyConstants.DEVICE_STATUS_KEY_PREFIX); + // 获取电量 + if (StringUtils.isNotBlank(deviceStatus)) { + JSONObject jsonObject = JSONObject.parseObject(deviceStatus); + vo.setBatteryPercentage(jsonObject.getInteger("batteryPercentage")); + vo.setChargeState(jsonObject.getInteger("chargeState")); + vo.setBatteryRemainingTime(jsonObject.getInteger("batteryRemainingTime")); + } else { + vo.setBatteryPercentage(0); + vo.setBatteryRemainingTime(0); + } + + + + String reportStatus = RedisUtils.getCacheObject(GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + device.getDeviceImei() + ":report"); + if (StringUtils.isNotBlank(reportStatus)) { + FuncType14StatusReport report = JSONObject.parseObject(reportStatus,FuncType14StatusReport.class); + FuncType14StatusReport.Data data = report.getData(); + FuncType14StatusReport.Brightness brightness = data.getBrightness(); + if(brightness != null){ + vo.setLightBrightness(brightness.getRed()); + } + + vo.setVolume(data.getVolume()); + FuncType14StatusReport.LedStrobe ledStrobe = data.getLedStrobe(); + if(ledStrobe != null){ + vo.setStrobeFrequency(ledStrobe.getFrequency()); + vo.setStrobeMode(ledStrobe.getMode()); + vo.setStrobeEnable(ledStrobe.getEnable()); + } + + + Integer voiceBroadcast = data.getVoiceBroadcast(); + if(voiceBroadcast != null){ + vo.setVoiceBroadcast(voiceBroadcast); + } + FuncType14StatusReport.SirenAlarm sirenAlarm = data.getSirenAlarm(); + if(sirenAlarm != null){ + vo.setVoiceStrobeAlarm(sirenAlarm.getEnable()); + vo.setAlarmMode(sirenAlarm.getMode()); + } + } + + String voicePlayRedisKey = GlobalConstants.GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + + device.getDeviceImei() + ":voicePlay"; + String voicePlayStatus = RedisUtils.getCacheObject(voicePlayRedisKey); + if(StringUtils.isNotBlank(voicePlayStatus)){ + FuncType6VoicePlayRequest funcType6VoicePlayRequest = JSONObject.parseObject(voicePlayStatus, FuncType6VoicePlayRequest.class); + if(funcType6VoicePlayRequest.getData() != null){ + vo.setVoiceBroadcast(funcType6VoicePlayRequest.getData().getVoiceBroadcast()); + } + } + + String strobeModeRedisKey = GlobalConstants.GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + + device.getDeviceImei() + ":strobeMode"; + String strobeModeStatus = RedisUtils.getCacheObject(strobeModeRedisKey); + if(StringUtils.isNotBlank(strobeModeStatus)){ + FuncType10StrobeModeRequest strobeModeRequest = JSONObject.parseObject(strobeModeStatus, FuncType10StrobeModeRequest.class); + FuncType10StrobeModeRequest.Data data = strobeModeRequest.getData(); + if(data != null){ + vo.setStrobeMode(strobeModeRequest.getData().getMode()); + vo.setStrobeEnable(strobeModeRequest.getData().getEnable()); + } + } + + String updateVolumeRedisKey = GlobalConstants.GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + + device.getDeviceImei() + ":updateVolume"; + String updateVolumeStatus = RedisUtils.getCacheObject(updateVolumeRedisKey); + if(StringUtils.isNotBlank(updateVolumeStatus)){ + FuncType9UpdateVolumeRequest updateVolumeRequest = JSONObject.parseObject(updateVolumeStatus, FuncType9UpdateVolumeRequest.class); + if(updateVolumeRequest.getData() != null){ + vo.setVolume(updateVolumeRequest.getData().getVolume()); + } + } + + String frequencyRedisKey = GlobalConstants.GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + + device.getDeviceImei() + ":frequency"; + String frequencyStatus = RedisUtils.getCacheObject(frequencyRedisKey); + if(StringUtils.isNotBlank(frequencyStatus)){ + FuncType11FrequencyRequest frequencyRequest = JSONObject.parseObject(frequencyStatus, FuncType11FrequencyRequest.class); + if(frequencyRequest.getData() != null){ + vo.setStrobeFrequency(frequencyRequest.getData().getFrequency()); + } + } + + String forceAudioRedisKey = GlobalConstants.GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + + device.getDeviceImei() + ":forceAudio"; + String forceAudioStatus = RedisUtils.getCacheObject(forceAudioRedisKey); + if(StringUtils.isNotBlank(forceAudioStatus)){ + FuncType12ForceAudioRequest forceAudioRequest = JSONObject.parseObject(forceAudioStatus, FuncType12ForceAudioRequest.class); + if(forceAudioRequest.getData() != null){ + vo.setVoiceStrobeAlarm(forceAudioRequest.getData().getVoiceStrobeAlarm()); + vo.setAlarmMode(forceAudioRequest.getData().getMode()); + } + } + + + // 获取经度纬度 + + String location = RedisUtils.getCacheObject(GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + device.getDeviceImei() + DEVICE_LOCATION_KEY_PREFIX); + if (StringUtils.isNotBlank(location)) { + JSONObject jsonObject = JSONObject.parseObject(location); + vo.setLatitude(jsonObject.getString("latitude")); + vo.setLongitude(jsonObject.getString("longitude")); + vo.setAddress(jsonObject.getString("address")); + } + + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("business_id", device.getId()); + queryWrapper.eq("file_type", 3); + queryWrapper.eq("use_status", 1); + List appBusinessFiles = appBusinessFileMapper.selectList(queryWrapper); + if(appBusinessFiles != null && appBusinessFiles.size() > 0){ + AppBusinessFile appBusinessFile = appBusinessFiles.get(0); + SysOssVo sysOssVo = sysOssMapper.selectVoById(appBusinessFile.getFileId()); + if(sysOssVo != null){ + vo.setVoiceResource(sysOssVo.getUrl()); + } + } + return vo; + } + + + private boolean getDeviceStatus(String deviceImei) { + String deviceOnlineStatusRedisKey = GlobalConstants.GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + deviceImei + DeviceRedisKeyConstants.DEVICE_ONLINE_STATUS_KEY_PREFIX; + return RedisUtils.getCacheObject(deviceOnlineStatusRedisKey) == null; + } + + + public void forceAlarmActivation(HBY100JForceAlarmActivationDto bo) { + List deviceIds = bo.getDeviceIds(); + if (deviceIds == null || deviceIds.isEmpty()) { + throw new ServiceException("请选择设备"); + } + AppLoginUser loginUser = AppLoginHelper.getLoginUser(); + bo.getDeviceIds().forEach(deviceId -> { + Device deviceObj = deviceMapper.selectById(deviceId); + if (getDeviceStatus(deviceObj.getDeviceImei())) { + throw new ServiceException(deviceObj.getDeviceName() + ",设备已断开连接"); + } + + FuncType12ForceAudioRequest request = new FuncType12ForceAudioRequest(); + request.setRequestId(GenerateIdUtil.generateNumericId()); + request.setImei(deviceObj.getDeviceImei()); + request.setFuncType("12"); + request.setTimestamp(System.currentTimeMillis()); + FuncType12ForceAudioRequest.Data data = new FuncType12ForceAudioRequest.Data(); + data.setVoiceStrobeAlarm(bo.getVoiceStrobeAlarm()); + data.setMode(bo.getMode()); + request.setData(data); + log.info("HBY100J强制报警,下发设备参数:{}", request); + mqttGateway.sendMsgToMqtt(buildMqttTopic(deviceObj.getDeviceImei()), 1, JSON.toJSONString(request)); + Long userId = loginUser == null ? pcUserId : loginUser.getUserId(); + + recordDeviceLog(deviceId, deviceObj.getDeviceName(), "强制报警激活", "强制报警激活", userId); + }); + + } + + public void updateVoice(HBY100JUpdateVoiceDto dto) { + AppBusinessFileVo appBusinessFileVo = appBusinessFileMapper.selectVoById(dto.getId()); + if(appBusinessFileVo == null){ + throw new ServiceException("文件不存在"); + } + Device deviceObj = deviceMapper.selectById(appBusinessFileVo.getBusinessId()); + if (getDeviceStatus(deviceObj.getDeviceImei())) { + throw new ServiceException(deviceObj.getDeviceName() + ",设备已断开连接"); + } + AppLoginUser loginUser = AppLoginHelper.getLoginUser(); + if(dto.getCommunicationMode()!=null && dto.getCommunicationMode()==0){ + SysOssVo sysOssVo = sysOssMapper.selectVoById(appBusinessFileVo.getFileId()); + FuncType5UpdateVoiceRequest request = new FuncType5UpdateVoiceRequest(); + request.setRequestId(GenerateIdUtil.generateNumericId()); + request.setImei(deviceObj.getDeviceImei()); + request.setFuncType("5"); + request.setTimestamp(System.currentTimeMillis()); + FuncType5UpdateVoiceRequest.Data data = new FuncType5UpdateVoiceRequest.Data(); + data.setVoiceResource(sysOssVo.getUrl()); + data.setVoiceType(0); + request.setData(data); + log.info("HBY100J更新语音,参数:{}", request); + + mqttGateway.sendMsgToMqtt(buildMqttTopic(deviceObj.getDeviceImei()), 1, JSON.toJSONString(request)); + } + + UpdateWrapper updateWrapper = new UpdateWrapper<>(); + updateWrapper.eq("business_id", appBusinessFileVo.getBusinessId()); + updateWrapper.set("use_status", 0); + appBusinessFileMapper.update(updateWrapper); + + UpdateWrapper updateWrapper2 = new UpdateWrapper<>(); + updateWrapper2.eq("id", appBusinessFileVo.getId()); + updateWrapper2.set("use_status", 1); + appBusinessFileMapper.update(updateWrapper2); + Long userId = loginUser == null ? pcUserId : loginUser.getUserId(); + recordDeviceLog(deviceObj.getId(), deviceObj.getDeviceName(), "更新语音", "更新语音", userId); + } + + private String buildMqttTopic(String deviceImei) { + String tenantId = LoginHelper.getTenantId(); + return MqttConstants.GLOBAL_PUB_KEY2 +tenantId + "/" + DEVICE_TYPE + deviceImei; + } + + public void strobeMode(HBY100JStrobeModeDto params) { + Device deviceObj = deviceMapper.selectById(params.getDeviceId()); + if (getDeviceStatus(deviceObj.getDeviceImei())) { + throw new ServiceException(deviceObj.getDeviceName() + ",设备已断开连接"); + } + AppLoginUser loginUser = AppLoginHelper.getLoginUser(); + FuncType10StrobeModeRequest request = new FuncType10StrobeModeRequest(); + request.setRequestId(GenerateIdUtil.generateNumericId()); + request.setImei(deviceObj.getDeviceImei()); + request.setFuncType("10"); + request.setTimestamp(System.currentTimeMillis()); + FuncType10StrobeModeRequest.Data data = new FuncType10StrobeModeRequest.Data(); + data.setMode(params.getMode()); + data.setEnable(params.getEnable()); + request.setData(data); + log.info("HBY100J爆闪模式开启/关闭,下发设备参数:{}", request); + mqttGateway.sendMsgToMqtt(buildMqttTopic(deviceObj.getDeviceImei()), 1, JSON.toJSONString(request)); + String content = params.getEnable() != null && params.getEnable() == 1 ? "爆闪模式开启" : "爆闪模式关闭"; + Long userId = loginUser == null ? pcUserId : loginUser.getUserId(); + recordDeviceLog(deviceObj.getId(), deviceObj.getDeviceName(), content, content, userId); + } + + public void lightAdjustment(HBY100JLightAdjustmentDto params) { + log.info("HBY100J灯光调节,请求参数:{}", params); + Device deviceObj = deviceMapper.selectById(params.getDeviceId()); + if (deviceObj == null) { + throw new ServiceException("设备不存在"); + } + if (getDeviceStatus(deviceObj.getDeviceImei())) { + throw new ServiceException(deviceObj.getDeviceName() + ",设备已断开连接"); + } + AppLoginUser loginUser = AppLoginHelper.getLoginUser(); + FuncType13BrightnessRequest request = new FuncType13BrightnessRequest(); + request.setRequestId(GenerateIdUtil.generateNumericId()); + request.setImei(deviceObj.getDeviceImei()); + request.setFuncType("13"); + request.setTimestamp(System.currentTimeMillis()); + FuncType13BrightnessRequest.Data data = new FuncType13BrightnessRequest.Data(); + data.setRed(params.getBrightness()); + data.setBlue(params.getBrightness()); + data.setYellow(params.getBrightness()); + request.setData(data); + log.info("HBY100J灯光调节,下发设备参数:{}", request); + mqttGateway.sendMsgToMqtt(buildMqttTopic(deviceObj.getDeviceImei()), 1, JSON.toJSONString(request)); + Long userId = loginUser == null ? pcUserId : loginUser.getUserId(); + recordDeviceLog(deviceObj.getId(), deviceObj.getDeviceName(), "灯光调节", "灯光调节", userId); + } + + public void strobeFrequency(HBY100JStrobeFrequencyDto params) { + Device deviceObj = deviceMapper.selectById(params.getDeviceId()); + if (deviceObj == null) { + throw new ServiceException("设备不存在"); + } + if (getDeviceStatus(deviceObj.getDeviceImei())) { + throw new ServiceException(deviceObj.getDeviceName() + ",设备已断开连接"); + } + AppLoginUser loginUser = AppLoginHelper.getLoginUser(); + FuncType11FrequencyRequest request = new FuncType11FrequencyRequest(); + request.setRequestId(GenerateIdUtil.generateNumericId()); + request.setImei(deviceObj.getDeviceImei()); + request.setFuncType("11"); + request.setTimestamp(System.currentTimeMillis()); + FuncType11FrequencyRequest.Data data = new FuncType11FrequencyRequest.Data(); + data.setFrequency(params.getFrequency()); + request.setData(data); + log.info("HBY100J爆闪频率,下发设备参数:{}", request); + mqttGateway.sendMsgToMqtt(buildMqttTopic(deviceObj.getDeviceImei()), 1, JSON.toJSONString(request)); + Long userId = loginUser == null ? pcUserId : loginUser.getUserId(); + recordDeviceLog(deviceObj.getId(), deviceObj.getDeviceName(), "爆闪频率", "爆闪频率", userId); + } + + public void updateVolume(HBY100JUpdateVolumeDto params) { + Device deviceObj = deviceMapper.selectById(params.getDeviceId()); + if (deviceObj == null) { + throw new ServiceException("设备不存在"); + } + if (getDeviceStatus(deviceObj.getDeviceImei())) { + throw new ServiceException(deviceObj.getDeviceName() + ",设备已断开连接"); + } + AppLoginUser loginUser = AppLoginHelper.getLoginUser(); + FuncType9UpdateVolumeRequest request = new FuncType9UpdateVolumeRequest(); + request.setRequestId(GenerateIdUtil.generateNumericId()); + request.setImei(deviceObj.getDeviceImei()); + request.setFuncType("9"); + request.setTimestamp(System.currentTimeMillis()); + FuncType9UpdateVolumeRequest.Data data = new FuncType9UpdateVolumeRequest.Data(); + data.setVolume(params.getVolume()); + request.setData(data); + log.info("HBY100J更新音量,下发设备参数:{}", JSON.toJSONString(request)); + mqttGateway.sendMsgToMqtt(buildMqttTopic(deviceObj.getDeviceImei()), 1, JSON.toJSONString(request)); + Long userId = loginUser == null ? pcUserId : loginUser.getUserId(); + recordDeviceLog(deviceObj.getId(), deviceObj.getDeviceName(), "更新音量", "更新音量", userId); + } + + public void voiceBroadcast(HBY100JVoiceBroadcastDto params) { + Device deviceObj = deviceMapper.selectById(params.getDeviceId()); + if (deviceObj == null) { + throw new ServiceException("设备不存在"); + } + if (getDeviceStatus(deviceObj.getDeviceImei())) { + throw new ServiceException(deviceObj.getDeviceName() + ",设备已断开连接"); + } + AppLoginUser loginUser = AppLoginHelper.getLoginUser(); + FuncType6VoicePlayRequest request = new FuncType6VoicePlayRequest(); + request.setRequestId(GenerateIdUtil.generateNumericId()); + request.setImei(deviceObj.getDeviceImei()); + request.setFuncType("6"); + request.setTimestamp(System.currentTimeMillis()); + FuncType6VoicePlayRequest.Data data = new FuncType6VoicePlayRequest.Data(); + data.setVoiceBroadcast(params.getVoiceBroadcast()); + request.setData(data); + log.info("HBY100J语音播报,下发设备参数:{}", request); + mqttGateway.sendMsgToMqtt(buildMqttTopic(deviceObj.getDeviceImei()), 1, JSON.toJSONString(request)); + Long userId = loginUser == null ? pcUserId : loginUser.getUserId(); + recordDeviceLog(deviceObj.getId(), deviceObj.getDeviceName(), "语音播报", "语音播报", userId); + } + + +} diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceXinghanBizService.java b/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceXinghanBizService.java index 3a92058e..0ac79432 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceXinghanBizService.java +++ b/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceXinghanBizService.java @@ -1,11 +1,11 @@ package com.fuyuanshen.web.service.device; import cn.hutool.core.bean.BeanUtil; -import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.lang.UUID; import cn.hutool.core.util.StrUtil; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONObject; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; @@ -15,20 +15,17 @@ import com.fuyuanshen.app.domain.AppPersonnelInfo; import com.fuyuanshen.app.domain.AppPersonnelInfoRecords; import com.fuyuanshen.app.domain.bo.AppPersonnelInfoBo; import com.fuyuanshen.app.domain.dto.AppDeviceLogoUploadDto; -import com.fuyuanshen.app.domain.dto.DeviceInstructDto; -import com.fuyuanshen.app.domain.vo.AppDeviceDetailVo; import com.fuyuanshen.app.domain.vo.AppPersonnelInfoVo; import com.fuyuanshen.app.mapper.AppPersonnelInfoMapper; import com.fuyuanshen.app.mapper.AppPersonnelInfoRecordsMapper; +import com.fuyuanshen.equipment.service.IAppBusinessFileService; +import com.fuyuanshen.equipment.service.IAppOperationVideoService; import com.fuyuanshen.common.core.constant.GlobalConstants; -import com.fuyuanshen.common.core.domain.R; import com.fuyuanshen.common.core.domain.model.AppLoginUser; -import com.fuyuanshen.common.core.domain.model.LoginUser; import com.fuyuanshen.common.core.exception.BadRequestException; import com.fuyuanshen.common.core.exception.ServiceException; import com.fuyuanshen.common.core.utils.ImageToCArrayConverter; import com.fuyuanshen.common.core.utils.MapstructUtils; -import com.fuyuanshen.common.core.utils.ObjectUtils; import com.fuyuanshen.common.core.utils.StringUtils; import com.fuyuanshen.common.json.utils.JsonUtils; import com.fuyuanshen.common.redis.utils.RedisUtils; @@ -40,7 +37,6 @@ import com.fuyuanshen.equipment.domain.dto.AppDeviceSendMsgBo; import com.fuyuanshen.equipment.domain.form.DeviceForm; import com.fuyuanshen.equipment.domain.query.DeviceTypeQueryCriteria; import com.fuyuanshen.equipment.enums.DeviceActiveStatusEnum; -import com.fuyuanshen.equipment.enums.LightModeEnum; import com.fuyuanshen.equipment.mapper.DeviceLogMapper; import com.fuyuanshen.equipment.mapper.DeviceMapper; import com.fuyuanshen.equipment.mapper.DeviceTypeGrantsMapper; @@ -51,26 +47,23 @@ import com.fuyuanshen.global.mqtt.base.MqttXinghanJson; import com.fuyuanshen.global.mqtt.config.MqttGateway; import com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants; import com.fuyuanshen.global.mqtt.constants.MqttConstants; -import com.fuyuanshen.system.domain.vo.SysOssVo; -import com.fuyuanshen.system.domain.vo.SysRoleVo; import com.fuyuanshen.web.domain.Dto.DeviceDebugLogoUploadDto; import com.fuyuanshen.web.domain.Dto.DeviceXinghanInstructDto; import com.fuyuanshen.web.domain.vo.DeviceXinghanDetailVo; import com.fuyuanshen.web.enums.AlarmTypeEnum; +import com.fuyuanshen.web.util.AliyunVoiceUtil; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; import java.time.Duration; import java.time.LocalDateTime; import java.util.*; -import java.util.stream.Collectors; import static com.fuyuanshen.common.core.constant.GlobalConstants.GLOBAL_REDIS_KEY; import static com.fuyuanshen.common.core.utils.ImageToCArrayConverter.convertHexToDecimal; @@ -91,8 +84,11 @@ public class DeviceXinghanBizService { private final IDeviceAlarmService deviceAlarmService; private final DeviceTypeGrantsMapper deviceTypeGrantsMapper; private final DeviceAssignmentsService deviceAssignmentsService; + private final IAppBusinessFileService appBusinessFileService; + private final IAppOperationVideoService appOperationVideoService; @Autowired private ObjectMapper objectMapper; + private final AliyunVoiceUtil voiceUtil; /** * 所有档位的描述表 @@ -135,7 +131,6 @@ public class DeviceXinghanBizService { public void upSOSGradeSettings(DeviceXinghanInstructDto dto) { if(dto.getIsBluetooth()){ long deviceId = dto.getDeviceId(); - // 1. 使用Optional简化空值检查,使代码更简洁 Device device = Optional.ofNullable(deviceMapper.selectById(deviceId)) .orElseThrow(() -> new ServiceException("设备不存在")); @@ -147,6 +142,24 @@ public class DeviceXinghanBizService { } } + /** + * 触发异步报警 + * Spring 会自动调用 AsyncConfig.getAsyncExecutor() 来执行此方法 + */ + @Async + public void executeSosCall(String phone) { + log.info("[SOS业务] 准备发起语音拨号 -> 目标: {}", phone); + Map params = Map.of("device", "670"); + String callId = voiceUtil.sendTtsSync(phone, "TTS_328730104", params); + + if (callId != null) { + log.info("[SOS业务] 拨号指令下发成功, callId: {}", callId); + // 这里可以记录拨打日志到数据库 + } else { + log.error("[SOS业务] 拨号指令下发失败,请检查配置或余额"); + } + } + /** * 设置强制报警 */ @@ -701,20 +714,31 @@ public class DeviceXinghanBizService { return deviceTypeMapper.findAll(criteria); } - // @Log("新增设备") - public void addDevice(DeviceForm deviceForm) { - if (deviceForm.getDeviceMac() != null && deviceForm.getBluetoothName() == null) { + /** + * 校验唯一性约束 + */ + private void validateDeviceUnique(DeviceForm form) { + if (form.getDeviceMac() != null && form.getBluetoothName() == null) { throw new BadRequestException("请填写蓝牙名称!!!"); } - Device device1 = deviceMapper.selectOne(new QueryWrapper().eq("device_mac", deviceForm.getDeviceMac())); - if (device1 != null) { + // 使用 QueryWrapper 替代 lambdaQuery() + Long macCount = deviceMapper.selectCount(new LambdaQueryWrapper() + .eq(Device::getDeviceMac, form.getDeviceMac())); + if (macCount > 0) { throw new BadRequestException("设备MAC已存在!!!"); } - Device device2 = deviceMapper.selectOne(new QueryWrapper().eq("device_imei", deviceForm.getDeviceImei())); - if (device2 != null) { + + Long imeiCount = deviceMapper.selectCount(new LambdaQueryWrapper() + .eq(Device::getDeviceImei, form.getDeviceImei())); + if (imeiCount > 0) { throw new BadRequestException("设备IMEI已存在!!!"); } + } + + // @Log("新增设备") + public void addDevice(DeviceForm deviceForm) { + validateDeviceUnique(deviceForm); DeviceTypeQueryCriteria queryCriteria = new DeviceTypeQueryCriteria(); queryCriteria.setDeviceTypeId(deviceForm.getDeviceType()); @@ -739,6 +763,7 @@ public class DeviceXinghanBizService { device.setCreateByName(loginUser.getNickname()); device.setTypeName(deviceTypes.getTypeName()); device.setDeviceType(deviceTypes.getId()); + device.setDevicePic(deviceTypes.getDevicePic()); if (device.getDeviceImei() != null) { device.setPubTopic("A/" + device.getDeviceImei()); device.setSubTopic("B/" + device.getDeviceImei()); @@ -747,6 +772,14 @@ public class DeviceXinghanBizService { device.setBindingStatus(0); deviceMapper.insert(device); + Long deviceId = device.getDeviceId(); + + // 查询设备类型的文件列表 + // 4. 核心优化:同步设备类型的文件列表 (一行代码) + appBusinessFileService.cloneFiles(deviceTypes.getId(), device.getId()); + //同步设备类型的视频列表 + appOperationVideoService.cloneFiles(deviceTypes.getId(), device.getId()); + // 新增设备类型记录 DeviceAssignments assignments = new DeviceAssignments(); assignments.setDeviceId(device.getId()); @@ -767,4 +800,23 @@ public class DeviceXinghanBizService { return uuidStr.replaceAll("-", ""); } + public Map GetDeviceByName(DeviceForm deviceForm){ + List> list= deviceMapper.GetDeviceByName(deviceForm); + Map device=null; + if(list!=null && list.size()>0){ + device=list.get(0); + } + return device; + } + + public int getEquipCountByType(DeviceForm form){ + var res=deviceMapper.getEquipCountByType(form); + return res; + } + + public List> getEquipAllByType(DeviceForm deviceForm){ + List> list= deviceMapper.getEquipAllByType(deviceForm); + return list; + } + } diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/util/AliyunVoiceUtil.java b/fys-admin/src/main/java/com/fuyuanshen/web/util/AliyunVoiceUtil.java new file mode 100644 index 00000000..1aac0670 --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/web/util/AliyunVoiceUtil.java @@ -0,0 +1,87 @@ +package com.fuyuanshen.web.util; + +import com.aliyun.dyvmsapi20170525.Client; +import com.aliyun.dyvmsapi20170525.models.SingleCallByTtsRequest; +import com.aliyun.dyvmsapi20170525.models.SingleCallByTtsResponse; +import com.aliyun.teaopenapi.models.Config; +import com.aliyun.teautil.models.RuntimeOptions; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +import java.util.Map; + +@Slf4j +@Component +public class AliyunVoiceUtil { + + private static final ObjectMapper objectMapper = new ObjectMapper(); + + @Value("${alibaba.tts.akId}") + private String akId; + @Value("${alibaba.tts.akSecret}") + private String akSecret; +// @Value("${alibaba.tts.calledShowNumber:}") + private String calledShowNumber; + + // ========== 核心:单例客户端(类似 OkHttpClient) ========== + private volatile Client client; + + /** + * 获取客户端(双重检查锁实现单例) + * 只有在第一次调用时才会根据配置实例化,后续直接返回复用 + */ + private Client getClient() throws Exception { + if (client == null) { + synchronized (this) { + if (client == null) { + log.info("[AliyunVoice] 正在初始化阿里云语音客户端..."); + Config config = new Config() + .setAccessKeyId(akId) + .setAccessKeySecret(akSecret) + .setEndpoint("dyvmsapi.aliyuncs.com"); + this.client = new Client(config); + } + } + } + return client; + } + + /** + * 同步发送方法:由异步架构调用 + */ + public String sendTtsSync(String phone, String templateCode, Map params) { + + try { + // 1. 获取(或初始化)单例客户端 + Client voiceClient = getClient(); + + SingleCallByTtsRequest request = new SingleCallByTtsRequest() + .setCalledNumber(phone) + .setTtsCode(templateCode) + .setTtsParam(objectMapper.writeValueAsString(params)); + + if (StringUtils.hasText(calledShowNumber)) { + request.setCalledShowNumber(calledShowNumber); + } + + // 生产级超时配置 + RuntimeOptions runtime = new RuntimeOptions(); + runtime.setConnectTimeout(5000); + runtime.setReadTimeout(10000); + + SingleCallByTtsResponse response = voiceClient.singleCallByTtsWithOptions(request, runtime); + + if ("OK".equalsIgnoreCase(response.getBody().getCode())) { + return response.getBody().getCallId(); + } else { + log.error("[AliyunVoice] 拨号失败: {}", response.getBody().getMessage()); + } + } catch (Exception e) { + log.error("[AliyunVoice] 接口异常", e); + } + return null; + } +} \ No newline at end of file diff --git a/fys-admin/src/main/resources/application-dev.yml b/fys-admin/src/main/resources/application-dev.yml index 169c4bfe..d934c905 100644 --- a/fys-admin/src/main/resources/application-dev.yml +++ b/fys-admin/src/main/resources/application-dev.yml @@ -279,11 +279,13 @@ justauth: # MQTT配置 mqtt: username: admin - password: fys123456 - url: tcp://47.107.152.87:1883 + password: #YtvpSfCNG + url: tcp://47.120.79.150:2883 subClientId: fys_subClient - subTopic: A/#,status/tenantCode/#,report/tenantCode/# - pubTopic: B/#,command/tenantCode/# + subTopic: A/#,status/# + pubTopic: B/#,command/# + subTopic2: regis/equip/# + pubTopic2: regis/# pubClientId: fys_pubClient diff --git a/fys-admin/src/main/resources/application-prod.yml b/fys-admin/src/main/resources/application-prod.yml index 33595ffc..84cfb50e 100644 --- a/fys-admin/src/main/resources/application-prod.yml +++ b/fys-admin/src/main/resources/application-prod.yml @@ -16,7 +16,7 @@ spring.boot.admin.client: --- # snail-job 配置 snail-job: - enabled: false + enabled: true # 需要在 SnailJob 后台组管理创建对应名称的组,然后创建任务的时候选择对应的组,才能正确分派任务 group: "fys_group" # SnailJob 接入验证令牌 详见 script/sql/ry_job.sql `sj_group_config`表 @@ -283,14 +283,16 @@ mqtt: password: #YtvpSfCNG url: tcp://47.120.79.150:3883 subClientId: fys_subClient - subTopic: A/#,status/tenantCode/#,report/tenantCode/# - pubTopic: B/#,command/tenantCode/# + subTopic: A/#,status/# + pubTopic: B/#,command/# + subTopic2: regis/equip/# + pubTopic2: regis/# pubClientId: fys_pubClient # TTS语音交互配置 alibaba: tts: - appKey: KTwSUKMrf2olFfjC - akId: LTAI5t6RsfCvQh57qojzbEoe - akSecret: MTqvK2mXYeCRkl1jVPndiNumyaad0R + appKey: lbGuq5K5bEH4uxmT + akId: LTAI5t66moCkhNC32TDJ5ReP + akSecret: 2F3sdoBJ08bYvJcuDgSkLnJwGXsvYH diff --git a/fys-common/fys-common-encrypt/src/main/java/com/fuyuanshen/common/encrypt/utils/EncryptUtilsTest.java b/fys-common/fys-common-encrypt/src/main/java/com/fuyuanshen/common/encrypt/utils/EncryptUtilsTest.java index c1b72532..f785cd45 100644 --- a/fys-common/fys-common-encrypt/src/main/java/com/fuyuanshen/common/encrypt/utils/EncryptUtilsTest.java +++ b/fys-common/fys-common-encrypt/src/main/java/com/fuyuanshen/common/encrypt/utils/EncryptUtilsTest.java @@ -39,8 +39,8 @@ public class EncryptUtilsTest { loginBody.setClientId("e5cd7e4891bf95d1d19206ce24a7b32e"); loginBody.setGrantType("password"); loginBody.setTenantId("894078"); - loginBody.setCode("15"); - loginBody.setUuid("28ecf3d396ce4e6db8eb414992235fad"); + loginBody.setCode("6"); + loginBody.setUuid("399d45194f3d4c7e9cbfb3294b198a9b"); // loginBody.setUsername("admin"); // loginBody.setPassword("admin123"); loginBody.setUsername("dyf"); diff --git a/fys-extend/fys-snailjob-server/src/main/resources/application-dev.yml b/fys-extend/fys-snailjob-server/src/main/resources/application-dev.yml index aaf47489..e051945b 100644 --- a/fys-extend/fys-snailjob-server/src/main/resources/application-dev.yml +++ b/fys-extend/fys-snailjob-server/src/main/resources/application-dev.yml @@ -4,7 +4,7 @@ spring: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true username: root - password: root + password: Jq_123456# hikari: connection-timeout: 30000 validation-timeout: 5000 diff --git a/fys-extend/fys-snailjob-server/src/main/resources/application-prod.yml b/fys-extend/fys-snailjob-server/src/main/resources/application-prod.yml index aaf47489..2e8da04e 100644 --- a/fys-extend/fys-snailjob-server/src/main/resources/application-prod.yml +++ b/fys-extend/fys-snailjob-server/src/main/resources/application-prod.yml @@ -2,9 +2,9 @@ spring: datasource: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true + url: jdbc:mysql://localhost:3306/fys-vue?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true username: root - password: root + password: Jq_123456# hikari: connection-timeout: 30000 validation-timeout: 5000 @@ -29,7 +29,7 @@ snail-job: --- # 监控中心配置 spring.boot.admin.client: # 增加客户端开关 - enabled: true + enabled: false url: http://localhost:9090/admin instance: service-host-type: IP diff --git a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/controller/AppBusinessFileController.java b/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/controller/AppBusinessFileController.java index 9ad7df31..195a719b 100644 --- a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/controller/AppBusinessFileController.java +++ b/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/controller/AppBusinessFileController.java @@ -17,9 +17,9 @@ import com.fuyuanshen.common.core.validate.AddGroup; import com.fuyuanshen.common.core.validate.EditGroup; import com.fuyuanshen.common.log.enums.BusinessType; import com.fuyuanshen.common.excel.utils.ExcelUtil; -import com.fuyuanshen.app.domain.vo.AppBusinessFileVo; -import com.fuyuanshen.app.domain.bo.AppBusinessFileBo; -import com.fuyuanshen.app.service.IAppBusinessFileService; +import com.fuyuanshen.equipment.domain.vo.AppBusinessFileVo; +import com.fuyuanshen.equipment.domain.bo.AppBusinessFileBo; +import com.fuyuanshen.equipment.service.IAppBusinessFileService; import com.fuyuanshen.common.mybatis.core.page.TableDataInfo; /** diff --git a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/controller/AppOperationVideoController.java b/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/controller/AppOperationVideoController.java index f6e0fb28..04123b4e 100644 --- a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/controller/AppOperationVideoController.java +++ b/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/controller/AppOperationVideoController.java @@ -17,9 +17,9 @@ import com.fuyuanshen.common.core.validate.AddGroup; import com.fuyuanshen.common.core.validate.EditGroup; import com.fuyuanshen.common.log.enums.BusinessType; import com.fuyuanshen.common.excel.utils.ExcelUtil; -import com.fuyuanshen.app.domain.vo.AppOperationVideoVo; -import com.fuyuanshen.app.domain.bo.AppOperationVideoBo; -import com.fuyuanshen.app.service.IAppOperationVideoService; +import com.fuyuanshen.equipment.domain.vo.AppOperationVideoVo; +import com.fuyuanshen.equipment.domain.bo.AppOperationVideoBo; +import com.fuyuanshen.equipment.service.IAppOperationVideoService; import com.fuyuanshen.common.mybatis.core.page.TableDataInfo; /** diff --git a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/domain/bo/AppDeviceBindRecordBo.java b/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/domain/bo/AppDeviceBindRecordBo.java index 4742933b..81a3ece6 100644 --- a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/domain/bo/AppDeviceBindRecordBo.java +++ b/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/domain/bo/AppDeviceBindRecordBo.java @@ -46,6 +46,9 @@ public class AppDeviceBindRecordBo extends BaseEntity { * 绑定时间 */ private Date bindingTime; - + /** + * 通讯方式 0:4G;1:蓝牙,2 4G&蓝牙 + */ + private Integer communicationMode; } diff --git a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/domain/vo/AppDeviceHBY100JDetailVo.java b/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/domain/vo/AppDeviceHBY100JDetailVo.java new file mode 100644 index 00000000..14d4765b --- /dev/null +++ b/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/domain/vo/AppDeviceHBY100JDetailVo.java @@ -0,0 +1,145 @@ +package com.fuyuanshen.app.domain.vo; + +import cn.idev.excel.annotation.ExcelProperty; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +@Data +public class AppDeviceHBY100JDetailVo implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + + /** + * 设备ID + */ + @ExcelProperty(value = "设备ID") + private Long deviceId; + + + /** + * 设备名称 + */ + private String deviceName; + + /** + * 设备IMEI + */ + private String deviceImei; + + /** + * 设备MAC + */ + private String deviceMac; + + /** + * 通讯方式 0:4G;1:蓝牙,2 4G&蓝牙 + */ + private Integer communicationMode; + + /** + * 设备图片 + */ + private String devicePic; + + /** + * 设备类型 + */ + private String typeName; + + /** + * 蓝牙名称 + */ + private String bluetoothName; + + //电量百分比 + /** + * 电量百分比 + */ + private Integer batteryPercentage; + + //充电状态(0没有充电,1正在充电,2为已充满) + /** + * 充电状态(0没有充电,1正在充电,2为已充满) + */ + private Integer chargeState; + + /** + * 在线状态(0离线,1在线) + */ + private Integer onlineStatus; + + // 经度 + /** + * 经度 + */ + private String longitude; + + // 纬度 + /** + * 纬度 + */ + private String latitude; + + // 逆解析地址 + /** + * 逆解析地址 + */ + private String address; + + // 亮度 + /** + * 亮度 + */ + private Integer lightBrightness; + + // 音量 + /** + * 音量 + */ + private Integer volume; + + + /** + * 语音资源URL(MP3等音频文件地址) + */ + private String voiceResource; + + // 报警模式 0公安,1 消防,2应急,3交警,4 市政,5 铁路,6 医疗,7 自定义语音 + /** + * 报警模式 0公安,1 消防,2应急,3交警,4 市政,5 铁路,6 医疗,7 自定义语音 + */ + private Integer alarmMode; + + // 强制报警开关: 0 关闭, 1开启 + /** + * 强制报警开关: 0 关闭, 1开启 + */ + private Integer voiceStrobeAlarm; + + /** + * 0 红色爆闪,1 蓝色爆闪,2 黄色爆闪,3,红色顺时针旋转爆闪,4黄色顺时针旋转爆闪,5,红蓝顺时针旋转爆闪,6 红蓝交替爆闪 + */ + private Integer strobeMode; + + /** + * "enable": 0爆闪关闭, 1爆闪开启 + */ + private Integer strobeEnable; + + /** + * 爆闪频率 + */ + private Integer strobeFrequency; + + /** + * 语音播报0 关闭, 1开启 + */ + private Integer voiceBroadcast; + + /** + * 续航时间(分钟) + */ + private Integer batteryRemainingTime; +} diff --git a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/domain/vo/AppDeviceShareVo.java b/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/domain/vo/AppDeviceShareVo.java index 92d515fe..c6a13bc0 100644 --- a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/domain/vo/AppDeviceShareVo.java +++ b/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/domain/vo/AppDeviceShareVo.java @@ -51,6 +51,11 @@ public class AppDeviceShareVo implements Serializable { @ExcelProperty(value = "设备名称") private String deviceName; + /** + * 设备mac地址 + */ + private String deviceMac; + /** * 手机号 */ diff --git a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/service/impl/AppDeviceBindRecordServiceImpl.java b/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/service/impl/AppDeviceBindRecordServiceImpl.java index bcfa1485..46123bd9 100644 --- a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/service/impl/AppDeviceBindRecordServiceImpl.java +++ b/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/service/impl/AppDeviceBindRecordServiceImpl.java @@ -76,6 +76,7 @@ public class AppDeviceBindRecordServiceImpl implements IAppDeviceBindRecordServi lqw.eq(bo.getDeviceId() != null, AppDeviceBindRecord::getDeviceId, bo.getDeviceId()); lqw.eq(bo.getBindingUserId() != null, AppDeviceBindRecord::getBindingUserId, bo.getBindingUserId()); lqw.eq(bo.getBindingTime() != null, AppDeviceBindRecord::getBindingTime, bo.getBindingTime()); + lqw.eq(bo.getCommunicationMode() != null, AppDeviceBindRecord::getCommunicationMode, bo.getCommunicationMode()); return lqw; } diff --git a/fys-modules/fys-customer/src/main/java/com/fuyuanshen/customer/service/impl/CustomerServiceImpl.java b/fys-modules/fys-customer/src/main/java/com/fuyuanshen/customer/service/impl/CustomerServiceImpl.java index 75ae1396..143acc30 100644 --- a/fys-modules/fys-customer/src/main/java/com/fuyuanshen/customer/service/impl/CustomerServiceImpl.java +++ b/fys-modules/fys-customer/src/main/java/com/fuyuanshen/customer/service/impl/CustomerServiceImpl.java @@ -16,10 +16,8 @@ import com.fuyuanshen.customer.domain.vo.ConsumerVo; import com.fuyuanshen.customer.mapper.CustomerMapper; import com.fuyuanshen.customer.service.CustomerService; import com.fuyuanshen.system.domain.SysUserRole; -import com.fuyuanshen.system.domain.bo.SysUserBo; import com.fuyuanshen.system.mapper.SysRoleMapper; import com.fuyuanshen.system.mapper.SysUserRoleMapper; -import com.fuyuanshen.system.service.ISysUserService; import com.fuyuanshen.system.service.impl.SysUserServiceImpl; import io.undertow.util.BadRequestException; import lombok.RequiredArgsConstructor; diff --git a/fys-modules/fys-equipment/pom.xml b/fys-modules/fys-equipment/pom.xml index 87edeb66..45956a69 100644 --- a/fys-modules/fys-equipment/pom.xml +++ b/fys-modules/fys-equipment/pom.xml @@ -18,6 +18,12 @@ + + org + jaudiotagger + 2.0.3 + compile + @@ -140,6 +146,18 @@ 3.3.1 + + + com.aliyun + dyvmsapi20170525 + 4.2.0 + + + com.aliyun + tea-openapi + 0.3.2 + + com.alibaba.fastjson2 diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/controller/DeviceController.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/controller/DeviceController.java index 56c605b8..2899ab69 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/controller/DeviceController.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/controller/DeviceController.java @@ -317,8 +317,7 @@ public class DeviceController extends BaseController { // 定义必需的表头 Set requiredHeaders = new HashSet<>(Arrays.asList( "设备名称", "设备类型名称", "设备图片", "设备MAC", "蓝牙名称", "设备IMEI", - "备注", "是否支持蓝牙", "定位方式", "通讯方式", - "型号字典用于APP页面跳转", "型号字典用于PC页面跳转" + "备注" )); // 检查必需的表头是否都存在 diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/controller/DeviceTypeController.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/controller/DeviceTypeController.java index 8aacedd2..630e4824 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/controller/DeviceTypeController.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/controller/DeviceTypeController.java @@ -15,6 +15,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +import java.io.IOException; import java.util.List; /** @@ -50,7 +51,7 @@ public class DeviceTypeController { // @Log("新增设备类型") @Operation(summary = "新增设备类型") @PostMapping(value = "/add") - public R createDeviceType(@Validated @RequestBody DeviceType resources) { + public R createDeviceType(@Validated @ModelAttribute DeviceTypeForm resources) throws IOException { deviceTypeService.create(resources); return R.ok(); } @@ -59,7 +60,7 @@ public class DeviceTypeController { // @Log("修改设备类型") @Operation(summary = "修改设备类型") @PutMapping(value = "/update") - public R updateDeviceType(@Validated @RequestBody DeviceTypeForm resources) { + public R updateDeviceType(@Validated @ModelAttribute DeviceTypeForm resources) throws IOException { deviceTypeService.update(resources); return R.ok(); } diff --git a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/domain/AppBusinessFile.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/AppBusinessFile.java similarity index 75% rename from fys-modules/fys-app/src/main/java/com/fuyuanshen/app/domain/AppBusinessFile.java rename to fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/AppBusinessFile.java index 307b4bbd..ed12fc59 100644 --- a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/domain/AppBusinessFile.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/AppBusinessFile.java @@ -1,4 +1,4 @@ -package com.fuyuanshen.app.domain; +package com.fuyuanshen.equipment.domain; import com.fuyuanshen.common.tenant.core.TenantEntity; import com.baomidou.mybatisplus.annotation.*; @@ -47,5 +47,18 @@ public class AppBusinessFile extends TenantEntity { */ private String remark; + /** + * 文件时长 + */ + private Integer duration; + /** + * 重命名 + */ + private String reName; + + /** + * 是否使用语音播报(0-否,1-是) + */ + private Integer useStatus; } diff --git a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/domain/AppOperationVideo.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/AppOperationVideo.java similarity index 95% rename from fys-modules/fys-app/src/main/java/com/fuyuanshen/app/domain/AppOperationVideo.java rename to fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/AppOperationVideo.java index 0a88c318..d61fe21c 100644 --- a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/domain/AppOperationVideo.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/AppOperationVideo.java @@ -1,4 +1,4 @@ -package com.fuyuanshen.app.domain; +package com.fuyuanshen.equipment.domain; import com.fuyuanshen.common.tenant.core.TenantEntity; import com.baomidou.mybatisplus.annotation.*; diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceRepairImages.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceRepairImages.java index d26082de..25465814 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceRepairImages.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceRepairImages.java @@ -10,32 +10,36 @@ import lombok.EqualsAndHashCode; /** * 设备维修图片对象 device_repair_images * - * * @date 2025-09-02 */ @Data @EqualsAndHashCode(callSuper = true) @TableName("device_repair_images") public class DeviceRepairImages extends TenantEntity { + /** * 维修图片ID */ @TableId(value = "image_id", type = IdType.AUTO) @TableField(insertStrategy = FieldStrategy.NEVER) private Long imageId; + /** * 维修记录ID */ @Schema(title = "维修记录ID") private Long recordId; + /** * 图片类型(维修前/维修后) */ @Schema(title = "图片类型(维修前/维修后)") private RepairImageType imageType; + /** * 图片存储路径 */ @Schema(title = "图片存储路径") private String imageUrl; + } diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceType.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceType.java index f2740237..034dad19 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceType.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/DeviceType.java @@ -85,5 +85,8 @@ public class DeviceType extends TenantEntity { @Schema(title = "型号字典用于PC页面跳转") private String pcModelDictionary; + @Schema(title = "设备图片") + private String devicePic; + } diff --git a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/domain/bo/AppBusinessFileBo.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/bo/AppBusinessFileBo.java similarity index 74% rename from fys-modules/fys-app/src/main/java/com/fuyuanshen/app/domain/bo/AppBusinessFileBo.java rename to fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/bo/AppBusinessFileBo.java index 0454e728..0fbecb1e 100644 --- a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/domain/bo/AppBusinessFileBo.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/bo/AppBusinessFileBo.java @@ -1,6 +1,6 @@ -package com.fuyuanshen.app.domain.bo; +package com.fuyuanshen.equipment.domain.bo; -import com.fuyuanshen.app.domain.AppBusinessFile; +import com.fuyuanshen.equipment.domain.AppBusinessFile; import com.fuyuanshen.common.core.validate.EditGroup; import com.fuyuanshen.common.mybatis.core.domain.BaseEntity; import io.github.linpeilie.annotations.AutoMapper; @@ -38,7 +38,7 @@ public class AppBusinessFileBo extends BaseEntity { private Long businessId; /** - * 文件类型(1:操作说明,2:产品参数) + * 文件类型(1:操作说明,2:产品参数,3:语音管理) */ private Long fileType; @@ -49,4 +49,13 @@ public class AppBusinessFileBo extends BaseEntity { private List ids; + + private Integer duration; + + /** + * 是否使用语音播报(0-否,1-是) + */ + private Integer useStatus; + + private Long createBy; } diff --git a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/domain/bo/AppOperationVideoBo.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/bo/AppOperationVideoBo.java similarity index 90% rename from fys-modules/fys-app/src/main/java/com/fuyuanshen/app/domain/bo/AppOperationVideoBo.java rename to fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/bo/AppOperationVideoBo.java index d15765fe..2388bee7 100644 --- a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/domain/bo/AppOperationVideoBo.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/bo/AppOperationVideoBo.java @@ -1,6 +1,6 @@ -package com.fuyuanshen.app.domain.bo; +package com.fuyuanshen.equipment.domain.bo; -import com.fuyuanshen.app.domain.AppOperationVideo; +import com.fuyuanshen.equipment.domain.AppOperationVideo; import com.fuyuanshen.common.core.validate.EditGroup; import com.fuyuanshen.common.mybatis.core.domain.BaseEntity; import io.github.linpeilie.annotations.AutoMapper; diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/dto/Hby100jForceAlarmBo.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/dto/Hby100jForceAlarmBo.java new file mode 100644 index 00000000..bb655428 --- /dev/null +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/dto/Hby100jForceAlarmBo.java @@ -0,0 +1,16 @@ +package com.fuyuanshen.equipment.domain.dto; + +import lombok.Data; + +import java.util.List; + +/** + * 绑定设备参数 + */ +@Data +public class Hby100jForceAlarmBo { + + private Long deviceId; + + +} diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/form/DeviceForm.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/form/DeviceForm.java index def13d17..50bcb58c 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/form/DeviceForm.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/form/DeviceForm.java @@ -54,6 +54,9 @@ public class DeviceForm { @Schema(title = "备注") private String remark; + @Schema(title = "商户号") + private Long tenant_id; + // 设备类型相关字段 @Schema(title = "设备类型名称") diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/form/DeviceTypeForm.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/form/DeviceTypeForm.java index 271611d9..2b5b533d 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/form/DeviceTypeForm.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/form/DeviceTypeForm.java @@ -2,6 +2,7 @@ package com.fuyuanshen.equipment.domain.form; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; +import org.springframework.web.multipart.MultipartFile; /** * @Description: 设备类型 @@ -48,5 +49,10 @@ public class DeviceTypeForm { */ @Schema(title = "型号字典用于PC页面跳转") private String pcModelDictionary; + @Schema(title = "设备图片") + private String devicePic; + + @Schema(title = "设备图片") + private MultipartFile file; } diff --git a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/domain/vo/AppBusinessFileVo.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/AppBusinessFileVo.java similarity index 82% rename from fys-modules/fys-app/src/main/java/com/fuyuanshen/app/domain/vo/AppBusinessFileVo.java rename to fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/AppBusinessFileVo.java index 66f10419..e0ea69ec 100644 --- a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/domain/vo/AppBusinessFileVo.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/AppBusinessFileVo.java @@ -1,17 +1,13 @@ -package com.fuyuanshen.app.domain.vo; +package com.fuyuanshen.equipment.domain.vo; -import com.fuyuanshen.app.domain.AppBusinessFile; +import com.fuyuanshen.equipment.domain.AppBusinessFile; import cn.idev.excel.annotation.ExcelIgnoreUnannotated; import cn.idev.excel.annotation.ExcelProperty; -import com.fuyuanshen.common.excel.annotation.ExcelDictFormat; -import com.fuyuanshen.common.excel.convert.ExcelDictConvert; import io.github.linpeilie.annotations.AutoMapper; import lombok.Data; import java.io.Serial; import java.io.Serializable; -import java.util.Date; - /** diff --git a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/domain/vo/AppFileVo.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/AppFileVo.java similarity index 62% rename from fys-modules/fys-app/src/main/java/com/fuyuanshen/app/domain/vo/AppFileVo.java rename to fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/AppFileVo.java index fdad1f1a..cd09ca77 100644 --- a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/domain/vo/AppFileVo.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/AppFileVo.java @@ -1,4 +1,4 @@ -package com.fuyuanshen.app.domain.vo; +package com.fuyuanshen.equipment.domain.vo; import lombok.Data; @@ -27,4 +27,16 @@ public class AppFileVo { * 文件url */ private String fileUrl; + + private Integer duration; + + /** + * 扩展文件名称 + */ + private String fileNameExt; + + /*** + * 是否使用语音播报(0-否,1-是) + */ + private Integer useStatus; } diff --git a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/domain/vo/AppOperationVideoVo.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/AppOperationVideoVo.java similarity index 81% rename from fys-modules/fys-app/src/main/java/com/fuyuanshen/app/domain/vo/AppOperationVideoVo.java rename to fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/AppOperationVideoVo.java index 3ee214e7..afd97780 100644 --- a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/domain/vo/AppOperationVideoVo.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/domain/vo/AppOperationVideoVo.java @@ -1,17 +1,13 @@ -package com.fuyuanshen.app.domain.vo; +package com.fuyuanshen.equipment.domain.vo; -import com.fuyuanshen.app.domain.AppOperationVideo; +import com.fuyuanshen.equipment.domain.AppOperationVideo; import cn.idev.excel.annotation.ExcelIgnoreUnannotated; import cn.idev.excel.annotation.ExcelProperty; -import com.fuyuanshen.common.excel.annotation.ExcelDictFormat; -import com.fuyuanshen.common.excel.convert.ExcelDictConvert; import io.github.linpeilie.annotations.AutoMapper; import lombok.Data; import java.io.Serial; import java.io.Serializable; -import java.util.Date; - /** diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/excel/UploadDeviceDataListener.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/excel/UploadDeviceDataListener.java index ba7e7b76..c6b84168 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/excel/UploadDeviceDataListener.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/excel/UploadDeviceDataListener.java @@ -12,6 +12,7 @@ import com.fuyuanshen.equipment.domain.DeviceType; import com.fuyuanshen.equipment.domain.dto.DeviceExcelImportDTO; import com.fuyuanshen.equipment.domain.dto.ImportResult; import com.fuyuanshen.equipment.domain.form.DeviceForm; +import com.fuyuanshen.equipment.domain.form.DeviceTypeForm; import com.fuyuanshen.equipment.handler.ImageWriteHandler; import com.fuyuanshen.system.domain.vo.SysOssVo; import lombok.extern.slf4j.Slf4j; @@ -437,8 +438,10 @@ public class UploadDeviceDataListener implements ReadListener { List queryAppFileList(AppBusinessFileBo bo); + } diff --git a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/mapper/AppOperationVideoMapper.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/AppOperationVideoMapper.java similarity index 65% rename from fys-modules/fys-app/src/main/java/com/fuyuanshen/app/mapper/AppOperationVideoMapper.java rename to fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/AppOperationVideoMapper.java index 65b7ef39..adf250db 100644 --- a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/mapper/AppOperationVideoMapper.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/AppOperationVideoMapper.java @@ -1,8 +1,8 @@ -package com.fuyuanshen.app.mapper; +package com.fuyuanshen.equipment.mapper; -import com.fuyuanshen.app.domain.AppOperationVideo; -import com.fuyuanshen.app.domain.vo.AppOperationVideoVo; import com.fuyuanshen.common.mybatis.core.mapper.BaseMapperPlus; +import com.fuyuanshen.equipment.domain.AppOperationVideo; +import com.fuyuanshen.equipment.domain.vo.AppOperationVideoVo; import org.apache.ibatis.annotations.Mapper; /** diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceMapper.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceMapper.java index 18fc9a26..465b7b28 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceMapper.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/mapper/DeviceMapper.java @@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.fuyuanshen.equipment.domain.Device; import com.fuyuanshen.equipment.domain.dto.InstructionRecordDto; +import com.fuyuanshen.equipment.domain.form.DeviceForm; import com.fuyuanshen.equipment.domain.query.DeviceQueryCriteria; import com.fuyuanshen.equipment.domain.vo.*; import org.apache.ibatis.annotations.Mapper; @@ -148,4 +149,10 @@ public interface DeviceMapper extends BaseMapper { */ int countByDeviceTypeId(@Param("deviceTypeId") Long deviceTypeId); + List> GetDeviceByName(DeviceForm deviceForm); + + int getEquipCountByType(DeviceForm deviceForm); + + List> getEquipAllByType(DeviceForm deviceForm); + } \ No newline at end of file diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/DeviceTypeService.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/DeviceTypeService.java index 2d75a0af..9d2e67c1 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/DeviceTypeService.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/DeviceTypeService.java @@ -8,6 +8,7 @@ import com.fuyuanshen.equipment.domain.DeviceType; import com.fuyuanshen.equipment.domain.form.DeviceTypeForm; import com.fuyuanshen.equipment.domain.query.DeviceTypeQueryCriteria; +import java.io.IOException; import java.util.List; /** @@ -63,14 +64,14 @@ public interface DeviceTypeService extends IService { * * @param resources / */ - void create(DeviceType resources); + void create(DeviceTypeForm resources) throws IOException; /** * 修改设备类型 * * @param resources / */ - void update(DeviceTypeForm resources); + void update(DeviceTypeForm resources) throws IOException; /** * 多选删除 diff --git a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/service/IAppBusinessFileService.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/IAppBusinessFileService.java similarity index 81% rename from fys-modules/fys-app/src/main/java/com/fuyuanshen/app/service/IAppBusinessFileService.java rename to fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/IAppBusinessFileService.java index 339f926d..e8b7380d 100644 --- a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/service/IAppBusinessFileService.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/IAppBusinessFileService.java @@ -1,11 +1,11 @@ -package com.fuyuanshen.app.service; +package com.fuyuanshen.equipment.service; -import com.fuyuanshen.app.domain.AppBusinessFile; -import com.fuyuanshen.app.domain.vo.AppBusinessFileVo; -import com.fuyuanshen.app.domain.bo.AppBusinessFileBo; -import com.fuyuanshen.app.domain.vo.AppFileVo; import com.fuyuanshen.common.mybatis.core.page.TableDataInfo; import com.fuyuanshen.common.mybatis.core.page.PageQuery; +import com.fuyuanshen.equipment.domain.AppBusinessFile; +import com.fuyuanshen.equipment.domain.bo.AppBusinessFileBo; +import com.fuyuanshen.equipment.domain.vo.AppBusinessFileVo; +import com.fuyuanshen.equipment.domain.vo.AppFileVo; import java.util.Collection; import java.util.List; @@ -57,7 +57,9 @@ public interface IAppBusinessFileService { * @param bo 批量新增app业务文件 * @return 是否新增成功 */ - Boolean insertBatch(Collection bo,Boolean isBatch); + Boolean insertBatch(Collection bo, Boolean isBatch); + + void cloneFiles(Long sourceId, Long targetId); /** * 修改app业务文件 @@ -78,4 +80,5 @@ public interface IAppBusinessFileService { List queryAppFileList(AppBusinessFileBo bo); + } diff --git a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/service/IAppOperationVideoService.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/IAppOperationVideoService.java similarity index 86% rename from fys-modules/fys-app/src/main/java/com/fuyuanshen/app/service/IAppOperationVideoService.java rename to fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/IAppOperationVideoService.java index ed38f869..cee052d6 100644 --- a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/service/IAppOperationVideoService.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/IAppOperationVideoService.java @@ -1,11 +1,10 @@ -package com.fuyuanshen.app.service; +package com.fuyuanshen.equipment.service; -import com.fuyuanshen.app.domain.AppBusinessFile; -import com.fuyuanshen.app.domain.AppOperationVideo; -import com.fuyuanshen.app.domain.vo.AppOperationVideoVo; -import com.fuyuanshen.app.domain.bo.AppOperationVideoBo; import com.fuyuanshen.common.mybatis.core.page.TableDataInfo; import com.fuyuanshen.common.mybatis.core.page.PageQuery; +import com.fuyuanshen.equipment.domain.AppOperationVideo; +import com.fuyuanshen.equipment.domain.bo.AppOperationVideoBo; +import com.fuyuanshen.equipment.domain.vo.AppOperationVideoVo; import java.util.Collection; import java.util.List; @@ -59,6 +58,8 @@ public interface IAppOperationVideoService { */ Boolean insertBatch(Collection bo); + void cloneFiles(Long sourceId, Long targetId); + /** * 修改操作视频 * diff --git a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/service/impl/AppBusinessFileServiceImpl.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/AppBusinessFileServiceImpl.java similarity index 71% rename from fys-modules/fys-app/src/main/java/com/fuyuanshen/app/service/impl/AppBusinessFileServiceImpl.java rename to fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/AppBusinessFileServiceImpl.java index c51fada1..aabbfd42 100644 --- a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/service/impl/AppBusinessFileServiceImpl.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/AppBusinessFileServiceImpl.java @@ -1,27 +1,29 @@ -package com.fuyuanshen.app.service.impl; +package com.fuyuanshen.equipment.service.impl; +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollUtil; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; -import com.fuyuanshen.app.domain.vo.AppFileVo; import com.fuyuanshen.common.core.utils.MapstructUtils; -import com.fuyuanshen.common.core.utils.StringUtils; import com.fuyuanshen.common.mybatis.core.page.TableDataInfo; import com.fuyuanshen.common.mybatis.core.page.PageQuery; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.fuyuanshen.equipment.domain.AppBusinessFile; +import com.fuyuanshen.equipment.domain.bo.AppBusinessFileBo; +import com.fuyuanshen.equipment.domain.vo.AppBusinessFileVo; +import com.fuyuanshen.equipment.domain.vo.AppFileVo; +import com.fuyuanshen.equipment.mapper.AppBusinessFileMapper; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; -import com.fuyuanshen.app.domain.bo.AppBusinessFileBo; -import com.fuyuanshen.app.domain.vo.AppBusinessFileVo; -import com.fuyuanshen.app.domain.AppBusinessFile; -import com.fuyuanshen.app.mapper.AppBusinessFileMapper; -import com.fuyuanshen.app.service.IAppBusinessFileService; +import com.fuyuanshen.equipment.service.IAppBusinessFileService; +import org.springframework.transaction.annotation.Transactional; -import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Collection; +import java.util.stream.Collectors; /** * app业务文件Service业务层处理 @@ -117,6 +119,38 @@ public class AppBusinessFileServiceImpl implements IAppBusinessFileService { return baseMapper.insertBatch(bo); } + /** + * 克隆业务文件列表到新业务ID + * @param sourceId 源业务ID(如设备类型ID) + * @param targetId 目标业务ID(如新设备ID) + */ + @Transactional(rollbackFor = Exception.class) + @Override + public void cloneFiles(Long sourceId, Long targetId) { + // 1. 使用 Wrappers 替代 this.lambdaQuery() + List sourceFiles = baseMapper.selectList( + Wrappers.lambdaQuery().eq(AppBusinessFile::getBusinessId, sourceId) + ); + + if (CollUtil.isEmpty(sourceFiles)) { + return; + } + + // 2. 批量转换并重置ID + List newFiles = sourceFiles.stream().map(file -> { + AppBusinessFile entity = new AppBusinessFile(); + // 建议使用你代码中已有的 MapstructUtils 或 BeanUtil + BeanUtil.copyProperties(file, entity); + entity.setId(null); // 确保主键自增 + entity.setBusinessId(targetId); // 绑定到新设备ID + return entity; + }).collect(Collectors.toList()); + + // 3. 使用你已有的 insertBatch 替代 saveBatch + // 注意:这里第二个参数传 false,因为是新设备,不需要执行你 insertBatch 里的删除逻辑 + this.insertBatch(newFiles, false); + } + /** * 修改app业务文件 * diff --git a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/service/impl/AppOperationVideoServiceImpl.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/AppOperationVideoServiceImpl.java similarity index 72% rename from fys-modules/fys-app/src/main/java/com/fuyuanshen/app/service/impl/AppOperationVideoServiceImpl.java rename to fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/AppOperationVideoServiceImpl.java index 32f8781e..12a7deb1 100644 --- a/fys-modules/fys-app/src/main/java/com/fuyuanshen/app/service/impl/AppOperationVideoServiceImpl.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/AppOperationVideoServiceImpl.java @@ -1,7 +1,8 @@ -package com.fuyuanshen.app.service.impl; +package com.fuyuanshen.equipment.service.impl; +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollUtil; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; -import com.fuyuanshen.app.domain.AppBusinessFile; import com.fuyuanshen.common.core.utils.MapstructUtils; import com.fuyuanshen.common.core.utils.StringUtils; import com.fuyuanshen.common.mybatis.core.page.TableDataInfo; @@ -9,18 +10,20 @@ import com.fuyuanshen.common.mybatis.core.page.PageQuery; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.fuyuanshen.equipment.domain.AppOperationVideo; +import com.fuyuanshen.equipment.domain.bo.AppOperationVideoBo; +import com.fuyuanshen.equipment.domain.vo.AppOperationVideoVo; +import com.fuyuanshen.equipment.mapper.AppOperationVideoMapper; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; -import com.fuyuanshen.app.domain.bo.AppOperationVideoBo; -import com.fuyuanshen.app.domain.vo.AppOperationVideoVo; -import com.fuyuanshen.app.domain.AppOperationVideo; -import com.fuyuanshen.app.mapper.AppOperationVideoMapper; -import com.fuyuanshen.app.service.IAppOperationVideoService; +import com.fuyuanshen.equipment.service.IAppOperationVideoService; +import org.springframework.transaction.annotation.Transactional; import java.util.List; import java.util.Map; import java.util.Collection; +import java.util.stream.Collectors; /** * 操作视频Service业务层处理 @@ -125,6 +128,37 @@ public class AppOperationVideoServiceImpl implements IAppOperationVideoService { return baseMapper.updateById(update) > 0; } + /** + * 克隆业务文件列表到新业务ID + * @param sourceId 源业务ID(如设备类型ID) + * @param targetId 目标业务ID(如新设备ID) + */ + @Transactional(rollbackFor = Exception.class) + @Override + public void cloneFiles(Long sourceId, Long targetId) { + // 1. 使用 Wrappers 替代 this.lambdaQuery() + List sourceFiles = baseMapper.selectList( + Wrappers.lambdaQuery().eq(AppOperationVideo::getDeviceId, sourceId) + ); + + if (CollUtil.isEmpty(sourceFiles)) { + return; + } + + // 2. 批量转换并重置ID + List newFiles = sourceFiles.stream().map(file -> { + AppOperationVideo entity = new AppOperationVideo(); + // 建议使用你代码中已有的 MapstructUtils 或 BeanUtil + BeanUtil.copyProperties(file, entity); + entity.setId(null); // 确保主键自增 + entity.setDeviceId(targetId); // 绑定到新设备ID + return entity; + }).collect(Collectors.toList()); + + // 3. 使用你已有的 insertBatch 替代 saveBatch + this.insertBatch(newFiles); + } + /** * 保存前的数据校验 */ diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceServiceImpl.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceServiceImpl.java index 797c4a09..c883cee2 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceServiceImpl.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceServiceImpl.java @@ -30,10 +30,7 @@ import com.fuyuanshen.equipment.enums.BindingStatusEnum; import com.fuyuanshen.equipment.enums.CommunicationModeEnum; import com.fuyuanshen.equipment.enums.DeviceActiveStatusEnum; import com.fuyuanshen.equipment.mapper.*; -import com.fuyuanshen.equipment.service.DeviceAssignmentsService; -import com.fuyuanshen.equipment.service.DeviceService; -import com.fuyuanshen.equipment.service.DeviceTypeGrantsService; -import com.fuyuanshen.equipment.service.IDeviceGeoFenceService; +import com.fuyuanshen.equipment.service.*; import com.fuyuanshen.equipment.utils.FileHashUtil; import com.fuyuanshen.system.domain.vo.SysOssVo; import com.fuyuanshen.system.domain.vo.SysRoleVo; @@ -77,6 +74,8 @@ public class DeviceServiceImpl extends ServiceImpl impleme private final DeviceFenceAccessRecordMapper deviceFenceAccessRecordMapper; private final FileHashUtil fileHashUtil; + private final IAppBusinessFileService appBusinessFileService; + private final IAppOperationVideoService appOperationVideoService; /** @@ -210,6 +209,20 @@ public class DeviceServiceImpl extends ServiceImpl impleme DeviceTypeGrants typeGrants = new DeviceTypeGrants(); + // 修改为: + Long userId; + LoginUser loginUser; + try { + loginUser = LoginHelper.getLoginUser(); + userId = loginUser != null ? loginUser.getUserId() : 1938143703108689922L; // 如果没有登录用户,使用默认系统用户ID + } catch (Exception e) { + userId = 1938143703108689922L; // 出现异常时使用默认系统用户ID + loginUser = new LoginUser(); + loginUser.setUserId(userId); + loginUser.setNickname("admin"); + loginUser.setTenantId("894078"); // 设置租户ID + } + if (deviceForm.getDeviceType() != null) { DeviceTypeQueryCriteria queryCriteria = new DeviceTypeQueryCriteria(); queryCriteria.setDeviceTypeId(deviceForm.getDeviceType()); @@ -282,8 +295,8 @@ public class DeviceServiceImpl extends ServiceImpl impleme throw new RuntimeException("设备类型名称已存在,无法新增!!!"); } - LoginUser loginUser = LoginHelper.getLoginUser(); newDeviceType.setCreateByName(loginUser.getNickname()); + newDeviceType.setTenantId(loginUser.getTenantId()); deviceTypeMapper.insert(newDeviceType); // 重新查询确保获取到正确的ID @@ -295,6 +308,7 @@ public class DeviceServiceImpl extends ServiceImpl impleme deviceTypeGrants.setCustomerId(loginUser.getUserId()); deviceTypeGrants.setGrantorCustomerId(loginUser.getUserId()); deviceTypeGrants.setGrantedAt(new Date()); + deviceTypeGrants.setTenantId(loginUser.getTenantId()); deviceTypeGrantsMapper.insert(deviceTypeGrants); } @@ -319,12 +333,13 @@ public class DeviceServiceImpl extends ServiceImpl impleme BeanUtil.copyProperties(deviceForm, device, true); device.setDeviceNo(createDeviceNo()); - LoginUser loginUser = LoginHelper.getLoginUser(); device.setCurrentOwnerId(loginUser.getUserId()); device.setOriginalOwnerId(loginUser.getUserId()); device.setCreateByName(loginUser.getNickname()); device.setTypeName(deviceType.getTypeName()); device.setDeviceType(deviceType.getId()); + device.setDevicePic(deviceType.getDevicePic()); + device.setTenantId(loginUser.getTenantId()); if (device.getDeviceImei() != null) { device.setPubTopic("A/" + device.getDeviceImei()); device.setSubTopic("B/" + device.getDeviceImei()); @@ -333,6 +348,12 @@ public class DeviceServiceImpl extends ServiceImpl impleme device.setBindingStatus(0); deviceMapper.insert(device); + // 查询设备类型的文件列表 + // 4. 核心优化:同步设备类型的文件列表 (一行代码) + appBusinessFileService.cloneFiles(deviceType.getId(), device.getId()); + // 同步设备类型的视频列表 + appOperationVideoService.cloneFiles(deviceType.getId(), device.getId()); + // 新增设备类型记录 DeviceAssignments assignments = new DeviceAssignments(); assignments.setDeviceId(device.getId()); @@ -345,10 +366,12 @@ public class DeviceServiceImpl extends ServiceImpl impleme assignments.setActive(DeviceActiveStatusEnum.ACTIVE.getCode()); String lever = USER_ID_SEPARATOR + loginUser.getUserId(); assignments.setLever(lever); + assignments.setTenantId(loginUser.getTenantId()); deviceAssignmentsService.save(assignments); } + private String createDeviceNo() { String uuidStr = UUID.fastUUID().toString(); // 获取带 - 的标准格式字符串 return uuidStr.replaceAll("-", ""); diff --git a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceTypeServiceImpl.java b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceTypeServiceImpl.java index b4628fa9..00428948 100644 --- a/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceTypeServiceImpl.java +++ b/fys-modules/fys-equipment/src/main/java/com/fuyuanshen/equipment/service/impl/DeviceTypeServiceImpl.java @@ -19,12 +19,16 @@ import com.fuyuanshen.equipment.mapper.DeviceMapper; import com.fuyuanshen.equipment.mapper.DeviceTypeGrantsMapper; import com.fuyuanshen.equipment.mapper.DeviceTypeMapper; import com.fuyuanshen.equipment.service.DeviceTypeService; +import com.fuyuanshen.equipment.utils.FileHashUtil; +import com.fuyuanshen.system.domain.vo.SysOssVo; import com.fuyuanshen.system.domain.vo.SysRoleVo; +import com.fuyuanshen.system.service.ISysOssService; import com.fuyuanshen.system.service.ISysRoleService; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.io.IOException; import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -46,6 +50,8 @@ public class DeviceTypeServiceImpl extends ServiceImpl typeName = deviceTypeMapper.selectList(new QueryWrapper().eq("type_name", resources.getTypeName())); if (CollectionUtil.isNotEmpty(typeName)) { throw new RuntimeException("设备类型名称已存在,无法新增!!!"); } + // 保存图片并获取URL + if (resources.getFile() != null) { + String fileHash = fileHashUtil.hash(resources.getFile()); + SysOssVo upload = ossService.updateHash(resources.getFile(), fileHash); + // 强制将HTTP替换为HTTPS + if (upload.getUrl() != null && upload.getUrl().startsWith("http://")) { + upload.setUrl(upload.getUrl().replaceFirst("^http://", "https://")); + } + // 设置图片路径 + resources.setDevicePic(upload.getUrl()); + } + + DeviceType deviceType = new DeviceType(); + BeanUtil.copyProperties(resources, deviceType, true); LoginUser loginUser = LoginHelper.getLoginUser(); - resources.setCustomerId(loginUser.getUserId()); - resources.setOwnerCustomerId(loginUser.getUserId()); - resources.setOriginalOwnerId(loginUser.getUserId()); - resources.setCreateByName(loginUser.getNickname()); - deviceTypeMapper.insert(resources); + deviceType.setCustomerId(loginUser.getUserId()); + deviceType.setOwnerCustomerId(loginUser.getUserId()); + deviceType.setOriginalOwnerId(loginUser.getUserId()); + deviceType.setCreateByName(loginUser.getNickname()); + deviceTypeMapper.insert(deviceType); // 自动授权给自己 DeviceTypeGrants deviceTypeGrants = new DeviceTypeGrants(); - deviceTypeGrants.setDeviceTypeId(resources.getId()); + deviceTypeGrants.setDeviceTypeId(deviceType.getId()); deviceTypeGrants.setCustomerId(loginUser.getUserId()); deviceTypeGrants.setGrantorCustomerId(loginUser.getUserId()); deviceTypeGrants.setGrantedAt(new Date()); @@ -213,7 +233,7 @@ public class DeviceTypeServiceImpl extends ServiceImpl 0) { - throw new RuntimeException("该设备类型下已有设备,无法修改设备类型名称!!!"); + throw new RuntimeException("该设备类型下已有设备,无法修改设备类型名称和路由跳转!!!"); } } @@ -244,6 +264,17 @@ public class DeviceTypeServiceImpl extends ServiceImpl - + - + select a.id,a.business_id,a.file_id,a.file_type,b.file_name,b.url fileUrl,a.duration, + CASE + WHEN a.re_name IS NULL THEN + SUBSTRING_INDEX(COALESCE(a.re_name, b.original_name), '.', 1) + ELSE + a.re_name + END AS fileNameExt,a.use_status + from app_business_file a left join sys_oss b on a.file_id = b.oss_id where 1=1 and a.business_id = #{businessId} @@ -16,8 +23,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" and a.file_type = #{fileType} - - and a.create_by = #{createBy} + + and (a.create_by = #{createBy} or a.create_by = 0) order by a.create_time desc diff --git a/fys-modules/fys-app/src/main/resources/mapper/app/AppOperationVideoMapper.xml b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/AppOperationVideoMapper.xml similarity index 67% rename from fys-modules/fys-app/src/main/resources/mapper/app/AppOperationVideoMapper.xml rename to fys-modules/fys-equipment/src/main/resources/mapper/equipment/AppOperationVideoMapper.xml index e303e121..4de2560f 100644 --- a/fys-modules/fys-app/src/main/resources/mapper/app/AppOperationVideoMapper.xml +++ b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/AppOperationVideoMapper.xml @@ -2,6 +2,6 @@ - + diff --git a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceMapper.xml b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceMapper.xml index cb9b26f3..8e10347d 100644 --- a/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceMapper.xml +++ b/fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceMapper.xml @@ -264,6 +264,44 @@ + + + + + +