ソースを参照

增加批量新增设备配置

hanzhengyi 1 日 前
コミット
5d75d07c4a

+ 58 - 0
API设计文档.md

@@ -990,6 +990,64 @@ Authorization: Bearer {token}
 | data | object | 新增结果对象 |
 | data.id | string | 新建设备主键id(对应 sas_device.id) |
 
+#### 7.2.1 批量新增设备配置
+
+**接口地址**: `POST /prod-api/service-sas/device/config/batch-add`
+
+**功能描述**: 批量新增同一设备编号下的多条设备记录,常用于一次性为 NVR 设备按通道生成多个设备。后端会根据 `channels` 字段,从 0 开始依次生成通道号为 `0 ~ channels-1` 的设备记录;若某个通道已存在相同 `deviceId + deviceType + channel` 的记录,则该通道会自动跳过,不重复插入。
+
+**请求参数**:
+
+```json
+{
+  "deviceId": "DEV001",
+  "deviceType": 1,
+  "ipAddr": "192.168.10.100",
+  "port": 8000,
+  "channels": 4,
+  "username": "admin",
+  "password": "password",
+  "address": "一楼机房",
+  "houseCode": "house_001",
+  "videoDeviceType": 2,
+  "videoProtocol": 2,
+  "note": "NVR 批量通道设备"
+}
+```
+
+**字段说明**:
+
+| 字段名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| deviceId | string | 是 | 设备编号(所有通道共用) |
+| deviceType | int | 是 | 设备类型编码(对应 `SystemTypeCodeEnum.code`) |
+| ipAddr | string | 是 | 设备 IP 地址 |
+| port | int | 是 | 设备端口 |
+| channels | int | 是 | 通道数量,>=1;将生成通道号为 0~channels-1 的多条记录 |
+| username | string | 否 | 登录用户名 |
+| password | string | 否 | 登录密码 |
+| address | string | 否 | 安装位置描述 |
+| houseCode | string | 否 | 部位编码(`sas_device.house_code`) |
+| videoDeviceType | int | 否 | 视频设备类型,如 1-IPC、2-NVR 等(对应 `sas_device.video_type`) |
+| videoProtocol | int | 否 | 视频协议,如 1-ONVIF、2-海康、3-大华 等(对应 `sas_device.video_protocol`) |
+| note | string | 否 | 备注说明 |
+
+**响应示例**:
+
+```json
+{
+  "status": "SUCCESS",
+  "code": 200,
+  "msg": "操作成功",
+  "data": null
+}
+```
+
+**补充说明**:
+
+- 判重规则:每个通道以 `deviceId + deviceType + channel` 作为唯一键;已存在的通道记录会被跳过,不抛错。
+- GIS 信息:批量新增接口不处理经纬度/高度,`gis_id` 为空,如需带坐标请使用“设备导入模板 + 批量导入”方式。
+
 #### 7.3 编辑设备配置
 
 **接口地址**: `PUT /prod-api/service-sas/device/config/{id}`

+ 16 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/controller/AlarmDataController.java

@@ -1,7 +1,13 @@
 package com.usky.cdi.controller;
 
 import com.usky.cdi.service.impl.AlarmDataTransferService;
+<<<<<<< Updated upstream
 import com.usky.cdi.service.vo.alarm.AlarmMessageVO;
+=======
+import com.usky.cdi.service.vo.alarm.AlarmMessage1VO;
+import com.usky.cdi.service.vo.alarm.AlarmMessageVO;
+import com.usky.cdi.service.vo.base.EngineeringBaseVO;
+>>>>>>> Stashed changes
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
@@ -24,7 +30,10 @@ import org.springframework.web.bind.annotation.RestController;
 public class AlarmDataController {
     @Autowired
     private AlarmDataTransferService alarmDataTransferService;
+<<<<<<< Updated upstream
 
+=======
+>>>>>>> Stashed changes
     /**
      * 上报人防工程基础信息
      */
@@ -38,6 +47,7 @@ public class AlarmDataController {
      * 上报人防工程基础信息
      */
     @PostMapping("/alarmMessage1")
+<<<<<<< Updated upstream
     public String sendAlarmMessage1(@RequestBody AlarmMessageVO vo) {
         boolean success = alarmDataTransferService.sendAlarmMessage1(vo);
         return success ? "上报成功" : "上报失败";
@@ -57,4 +67,10 @@ public class AlarmDataController {
         boolean success = alarmDataTransferService.sendEngineeringBase(vo);
         return success ? "上报成功" : "上报失败";
     }
+=======
+    public String sendAlarmMessage1(@RequestBody AlarmMessage1VO vo) {
+        boolean success = alarmDataTransferService.sendAlarmMessage1(vo);
+        return success ? "上报成功" : "上报失败";
+    }
+>>>>>>> Stashed changes
 }

+ 10 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/controller/MybatisGeneratorUtils.java

@@ -43,10 +43,16 @@ public class MybatisGeneratorUtils {
         //2、数据源配置
         //修改数据源
         DataSourceConfig dsc = new DataSourceConfig();
+<<<<<<< Updated upstream
         dsc.setUrl("jdbc:mysql://47.111.81.118:13307/usky-cloud?useUnicode=true&serverTimezone=GMT&useSSL=false&characterEncoding=utf8");
         dsc.setDriverName("com.mysql.jdbc.Driver");
         dsc.setUsername("usky");
         dsc.setPassword("Yt#75Usky");
+=======
+        dsc.setUrl("jdbc:mysql://192.168.10.165:3306/usky-cloud?useUnicode=true&serverTimezone=GMT&useSSL=false&characterEncoding=utf8");
+        dsc.setUsername("root");
+        dsc.setPassword("yt123456");
+>>>>>>> Stashed changes
         mpg.setDataSource(dsc);
 
         // 3、包配置
@@ -71,7 +77,11 @@ public class MybatisGeneratorUtils {
         // strategy.setTablePrefix("t_"); // 表名前缀
         strategy.setEntityLombokModel(true); //使用lombok
         //修改自己想要生成的表
+<<<<<<< Updated upstream
         strategy.setInclude("dmp_product");  // 逆向工程使用的表   如果要生成多个,这里可以传入String[]
+=======
+        strategy.setInclude("dmp_device");  // 逆向工程使用的表   如果要生成多个,这里可以传入String[]
+>>>>>>> Stashed changes
         mpg.setStrategy(strategy);
 
         // 关闭默认 xml 生成,调整生成 至 根目录

+ 11 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/config/mqtt/MqttOutConfig.java

@@ -1,6 +1,12 @@
 package com.usky.cdi.service.config.mqtt;
 
+<<<<<<< Updated upstream
 import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
+=======
+import com.alibaba.fastjson.JSONObject;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+>>>>>>> Stashed changes
 import org.springframework.context.annotation.Bean;
 import org.springframework.integration.annotation.MessagingGateway;
 import org.springframework.integration.annotation.ServiceActivator;
@@ -15,6 +21,8 @@ import org.springframework.stereotype.Component;
 
 import java.util.Map;
 
+import java.util.Map;
+
 /**
  * @author han
  * @date 2025/03/20 14:31
@@ -61,6 +69,7 @@ public class MqttOutConfig {
         return messageHandler;
     }
 
+<<<<<<< Updated upstream
     /**
      * MQTT客户端工厂
      * 注意:这个方法会被Spring自动创建,用于创建MQTT客户端
@@ -86,6 +95,8 @@ public class MqttOutConfig {
 
     // 注意:这个接口需要被Spring扫描到,所以我们保留@MessagingGateway注解
     // Spring会自动创建这个接口的实现类
+=======
+>>>>>>> Stashed changes
     @MessagingGateway(defaultRequestChannel = CHANNEL_NAME_OUT)
     public interface MqttGateway {
         /**

+ 63 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/impl/AlarmDataTransferService.java

@@ -2,6 +2,7 @@ package com.usky.cdi.service.impl;
 
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONObject;
+<<<<<<< Updated upstream
 // import com.alibaba.nacos.shaded.com.google.gson.Gson;
 import com.usky.cdi.service.config.mqtt.MqttOutConfig;
 import com.usky.cdi.service.mqtt.MqttConnectionTool;
@@ -9,12 +10,27 @@ import com.usky.cdi.service.util.SnowflakeIdGenerator;
 import com.usky.cdi.service.vo.alarm.AlarmMessageVO;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
+=======
+import com.alibaba.nacos.shaded.com.google.gson.Gson;
+import com.usky.cdi.service.config.mqtt.MqttOutConfig;
+import com.usky.cdi.service.util.SnowflakeIdGenerator;
+import com.usky.cdi.service.vo.alarm.AlarmMessage1VO;
+import com.usky.cdi.service.vo.alarm.AlarmMessageVO;
+import com.usky.cdi.service.vo.base.FloorPlaneVO;
+import lombok.extern.slf4j.Slf4j;
+>>>>>>> Stashed changes
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.stereotype.Service;
 
 import javax.annotation.Resource;
+<<<<<<< Updated upstream
 import java.text.SimpleDateFormat;
 import java.time.LocalDateTime;
+=======
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.text.SimpleDateFormat;
+>>>>>>> Stashed changes
 import java.util.Date;
 import java.util.HashMap;
 
@@ -27,10 +43,16 @@ import java.util.HashMap;
  */
 @Slf4j
 @Service
+<<<<<<< Updated upstream
 public class AlarmDataTransferService {
 
     @Autowired
     private MqttConnectionTool mqttConnectionTool;
+=======
+@ConditionalOnProperty(prefix = "mqtt", value = {"enabled"}, havingValue = "true")
+public class AlarmDataTransferService {
+
+>>>>>>> Stashed changes
     @Resource
     private MqttOutConfig.MqttGateway mqttGateway;
 
@@ -39,9 +61,14 @@ public class AlarmDataTransferService {
 
     public AlarmDataTransferService() {
         // 使用默认的workerId和datacenterId,实际项目中可以从配置读取
+<<<<<<< Updated upstream
         this.idGenerator = new SnowflakeIdGenerator(3L, 3L);
     }
 
+=======
+        this.idGenerator = new SnowflakeIdGenerator(1L, 1L);
+    }
+>>>>>>> Stashed changes
     /**
      * 获取当前时间字符串
      */
@@ -69,10 +96,17 @@ public class AlarmDataTransferService {
                 vo.setDataPacketID(generateDataPacketID());
             }
             if (vo.getPublishTime() == null) {
+<<<<<<< Updated upstream
                 vo.setPublishTime(LocalDateTime.now());
             }
 
 //            HashMap<String, Object> map = new HashMap<>();
+=======
+                vo.setPublishTime(getCurrentTime());
+            }
+
+            HashMap<String, Object> map = new HashMap<>();
+>>>>>>> Stashed changes
 //            map.put("dataPacketID", vo.getDataPacketID());
 //            map.put("engineeringID", vo.getEngineeringID());
 //            map.put("floor", vo.getFloor());
@@ -83,6 +117,7 @@ public class AlarmDataTransferService {
 //            map.put("filePixHeight", vo.getFilePixHeight());
 //            map.put("floorFile", imageBytes);
 //            map.put("publishTime", vo.getPublishTime());
+<<<<<<< Updated upstream
 //            Gson gson = new Gson();
             JSONObject jsonObject = (JSONObject) JSON.toJSON(vo);
             String json = jsonObject.toJSONString();
@@ -90,6 +125,14 @@ public class AlarmDataTransferService {
             MqttConnectionTool.MqttGateway IQeIRyXG = mqttConnectionTool.connectOrRefresh("3101070011", "5RqhJ7VG");
             String topic = "alarm/message";
             IQeIRyXG.sendToMqtt(topic, json);
+=======
+            Gson gson = new Gson();
+            JSONObject jsonObject = (JSONObject) JSON.toJSON(vo);
+            String json = jsonObject.toJSONString();
+            System.out.println(json);
+            String topic = "alarm/message";
+            mqttGateway.sendToMqtt(topic, json);
+>>>>>>> Stashed changes
 
             return true;
         } catch (Exception e) {
@@ -105,15 +148,23 @@ public class AlarmDataTransferService {
      * @param vo 楼层平面图信息
      * @return 是否发送成功
      */
+<<<<<<< Updated upstream
     public boolean sendAlarmMessage1(AlarmMessageVO vo) {
         MqttConnectionTool.MqttGateway IQeIRyXG = mqttConnectionTool.connectOrRefresh("3101070011", "5RqhJ7VG");
 
+=======
+    public boolean sendAlarmMessage1(AlarmMessage1VO vo) {
+>>>>>>> Stashed changes
         try {
             if (vo.getDataPacketID() == null) {
                 vo.setDataPacketID(generateDataPacketID());
             }
             if (vo.getPublishTime() == null) {
+<<<<<<< Updated upstream
                 vo.setPublishTime(LocalDateTime.now());
+=======
+                vo.setPublishTime(getCurrentTime());
+>>>>>>> Stashed changes
             }
 
             HashMap<String, Object> map = new HashMap<>();
@@ -127,6 +178,7 @@ public class AlarmDataTransferService {
 //            map.put("filePixHeight", vo.getFilePixHeight());
 //            map.put("floorFile", imageBytes);
 //            map.put("publishTime", vo.getPublishTime());
+<<<<<<< Updated upstream
 //             Gson gson = new Gson();
             JSONObject jsonObject = (JSONObject) JSON.toJSON(vo);
             String json = jsonObject.toJSONString();
@@ -156,6 +208,14 @@ public class AlarmDataTransferService {
             MqttConnectionTool.MqttGateway IQeIRyXG = mqttConnectionTool.connectOrRefresh("3101070011", "5RqhJ7VG");
             String topic = "alarm/message";
             IQeIRyXG.sendToMqtt(topic, json);
+=======
+            Gson gson = new Gson();
+            JSONObject jsonObject = (JSONObject) JSON.toJSON(vo);
+            String json = jsonObject.toJSONString();
+            System.out.println(json);
+            String topic = "alarm/message";
+            mqttGateway.sendToMqtt(topic, json);
+>>>>>>> Stashed changes
 
             return true;
         } catch (Exception e) {
@@ -163,6 +223,7 @@ public class AlarmDataTransferService {
             return false;
         }
     }
+<<<<<<< Updated upstream
 
     public boolean sendEngineeringBase(AlarmMessageVO vo) {
         try {
@@ -187,4 +248,6 @@ public class AlarmDataTransferService {
             return false;
         }
     }
+=======
+>>>>>>> Stashed changes
 }

+ 5 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/impl/BaseDataTransferService.java

@@ -224,8 +224,13 @@ public class BaseDataTransferService {
             userIdToName.put(709, 15);
             userIdToName.put(710, 16);
             userIdToName.put(711, 2);
+<<<<<<< Updated upstream
             //userIdToName.put(712, 34);
             //userIdToName.put(713, 36);
+=======
+            userIdToName.put(712, 34);
+            userIdToName.put(713, 36);
+>>>>>>> Stashed changes
             userIdToName.put(714, 37);
 
             HashMap<String, Object> map = new HashMap<>();

+ 13 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/impl/IotDataTransferService.java

@@ -2,11 +2,15 @@ package com.usky.cdi.service.impl;
 
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONObject;
+<<<<<<< Updated upstream
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.usky.cdi.domain.DmpDevice;
 import com.usky.cdi.domain.DmpProduct;
 import com.usky.cdi.mapper.DmpDeviceMapper;
 import com.usky.cdi.mapper.DmpProductMapper;
+=======
+//import com.usky.cdi.service.config.mqtt.MqttGateway;
+>>>>>>> Stashed changes
 import com.usky.cdi.service.config.mqtt.MqttOutConfig;
 import com.usky.cdi.service.enums.EnvMonitorMqttTopic;
 import com.usky.cdi.service.mqtt.MqttConnectionTool;
@@ -21,8 +25,12 @@ import org.springframework.context.ApplicationContext;
 import org.springframework.integration.mqtt.core.DefaultMqttPahoClientFactory;
 import org.springframework.stereotype.Service;
 
+<<<<<<< Updated upstream
 import javax.annotation.PostConstruct;
 
+=======
+import javax.annotation.Resource;
+>>>>>>> Stashed changes
 import java.time.Instant;
 import java.time.LocalDateTime;
 import java.time.ZoneId;
@@ -39,6 +47,7 @@ import java.util.stream.Collectors;
 @Service
 public class IotDataTransferService {
 
+<<<<<<< Updated upstream
     private MqttOutConfig.MqttGateway mqttGateway;
 
     @Autowired
@@ -57,6 +66,10 @@ public class IotDataTransferService {
     // 存储每个任务的MQTT客户端工厂和网关
     private final Map<String, MqttConnectionTool.MqttGateway> mqttGatewayMap = new ConcurrentHashMap<>();
     private final Map<String, DefaultMqttPahoClientFactory> mqttClientFactoryMap = new ConcurrentHashMap<>();
+=======
+    @Resource
+    private MqttOutConfig.MqttGateway mqttGateway;
+>>>>>>> Stashed changes
 
     private SnowflakeIdGenerator idGenerator;
 

+ 69 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/alarm/AlarmMessage1VO.java

@@ -0,0 +1,69 @@
+package com.usky.cdi.service.vo.alarm;
+
+import lombok.Data;
+
+@Data
+public class AlarmMessage1VO {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 数据包ID
+     */
+    private Long dataPacketID;
+
+    /**
+     * 人防工程ID
+     */
+    private Long engineeringID;
+
+    /**
+     * 事件ID
+     */
+    private Integer alarmID;
+
+    /**
+     * 事件来源
+     */
+    private Integer alarmSource;
+
+    /**
+     * 物联设施ID
+     */
+    private Integer sensorID;
+
+    /**
+     * 事件类型
+     */
+    private String alarmType;
+
+    /**
+     * 事件状态
+     */
+    private Integer alarmStatus;
+
+    /**
+     * 最新水浸状态
+     */
+    private Integer sensorValue;
+
+    /**
+     * 事件发生/更新时间
+     */
+    private String alarmUpdateTime;
+
+    /**
+     * 监测对象编号
+     */
+    private String monitorObjNo;
+
+    /**
+     * 事件描述
+     */
+    private String alarmDesc;
+
+    /**
+     * 上报时间
+     */
+    private String publishTime;
+
+}

+ 74 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/alarm/AlarmMessageVO.java

@@ -1,5 +1,6 @@
 package com.usky.cdi.service.vo.alarm;
 
+<<<<<<< Updated upstream
 import com.fasterxml.jackson.annotation.JsonFormat;
 import com.fasterxml.jackson.annotation.JsonInclude;
 import lombok.Data;
@@ -143,4 +144,77 @@ public class AlarmMessageVO<T extends Number> implements Serializable {
      * Int 10
      **/
     private Integer lineNo;
+=======
+import lombok.Data;
+
+@Data
+public class AlarmMessageVO {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 数据包ID
+     */
+    private Long dataPacketID;
+
+    /**
+     * 人防工程ID
+     */
+    private Long engineeringID;
+
+    /**
+     * 事件ID
+     */
+    private Integer alarmID;
+
+    /**
+     * 事件来源
+     */
+    private Integer alarmSource;
+
+    /**
+     * 物联设施ID
+     */
+    private Integer sensorID;
+
+    /**
+     * 事件类型
+     */
+    private String alarmType;
+
+    /**
+     * 事件状态
+     */
+    private Integer alarmStatus;
+
+    /**
+     * 最新水浸状态
+     */
+    private Double sensorValue;
+
+    /**
+     * 告警阈值
+     */
+    private Double thresholding;
+
+    /**
+     * 事件发生/更新时间
+     */
+    private String alarmUpdateTime;
+
+    /**
+     * 监测对象编号
+     */
+    private String monitorObjNo;
+
+    /**
+     * 事件描述
+     */
+    private String alarmDesc;
+
+    /**
+     * 上报时间
+     */
+    private String publishTime;
+
+>>>>>>> Stashed changes
 }

+ 10 - 0
service-sas/service-sas-biz/src/main/java/com/usky/sas/controller/web/SasDeviceController.java

@@ -36,6 +36,7 @@ import java.util.List;
  * POST /prod-api/service-sas/device/syncAgboxDevice    同步AG设备
  * GET  /prod-api/service-sas/device/import/template    获取设备导入模板(Excel)
  * POST /prod-api/service-sas/device/importBatchDeviceByExcel 批量设备导入
+ * POST /prod-api/service-sas/device/config/batch-add   批量新增设备配置
  */
 @RestController
 @RequestMapping("/device")
@@ -67,6 +68,15 @@ public class SasDeviceController {
         return ApiResult.success(resp);
     }
 
+    /**
+     * 批量新增设备配置(按 channels 生成多条记录)
+     */
+    @PostMapping("/config/batch-add")
+    public ApiResult<Void> addDeviceBatch(@RequestBody DeviceBatchAddRequest request) {
+        sasDeviceService.addDeviceBatch(request);
+        return ApiResult.success();
+    }
+
     @PutMapping("/config/{id}")
     public ApiResult<Void> updateConfig(@PathVariable("id") String id, @RequestBody DeviceConfigSaveRequest request) {
         sasDeviceService.updateConfig(id, request);

+ 3 - 0
service-sas/service-sas-biz/src/main/java/com/usky/sas/service/SasDeviceService.java

@@ -59,5 +59,8 @@ public interface SasDeviceService extends CrudService<SasDevice> {
 
     /** 通过 Excel 批量导入设备 */
     String importDeviceByExcel(MultipartFile file);
+
+    /** 批量新增设备(按 channels 生成多条记录) */
+    void addDeviceBatch(DeviceBatchAddRequest request);
 }
 

+ 43 - 0
service-sas/service-sas-biz/src/main/java/com/usky/sas/service/impl/SasDeviceServiceImpl.java

@@ -480,6 +480,49 @@ public class SasDeviceServiceImpl extends AbstractCrudService<SasDeviceMapper, S
         throw new RuntimeException("AG 接口请求失败,重试 " + MAX_RETRIES + " 次后仍失败", lastEx);
     }
 
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void addDeviceBatch(DeviceBatchAddRequest request) {
+        if (request == null
+                || request.getDeviceId() == null || request.getDeviceId().isEmpty()
+                || request.getDeviceType() == null
+                || request.getIpAddr() == null || request.getIpAddr().isEmpty()
+                || request.getPort() == null
+                || request.getChannels() == null || request.getChannels() <= 0) {
+            throw new BusinessException("批量新增设备参数不完整,设备编号、设备类型、IP、端口、通道数量为必填");
+        }
+        int channels = request.getChannels();
+        for (int ch = 0; ch < channels; ch++) {
+            LambdaQueryWrapper<SasDevice> wrapper = new LambdaQueryWrapper<>();
+            wrapper.eq(SasDevice::getDeviceId, request.getDeviceId())
+                    .eq(SasDevice::getDeviceType, request.getDeviceType())
+                    .eq(SasDevice::getChannel, ch);
+            if (this.count(wrapper) > 0) {
+                continue;
+            }
+            SasDevice device = new SasDevice();
+            device.setId(UUID.randomUUID().toString());
+            device.setDeviceId(request.getDeviceId());
+            device.setDeviceType(request.getDeviceType());
+            device.setChannel(ch);
+            device.setIpAddr(request.getIpAddr());
+            device.setPort(request.getPort());
+            device.setUsername(request.getUsername());
+            device.setPassword(request.getPassword());
+            device.setVideoType(request.getVideoDeviceType());
+            device.setVideoProtocol(request.getVideoProtocol());
+            device.setAddress(request.getAddress());
+            device.setHouseCode(request.getHouseCode());
+            device.setNote(request.getNote());
+            device.setIsBinding(false);
+            device.setShield(false);
+            LocalDateTime now = LocalDateTime.now();
+            device.setCreateTime(now);
+            device.setUpdateTime(now);
+            this.save(device);
+        }
+    }
+
     @Override
     @Transactional(rollbackFor = Exception.class)
     public String importDeviceByExcel(MultipartFile file) {

+ 47 - 0
service-sas/service-sas-biz/src/main/java/com/usky/sas/service/vo/DeviceBatchAddRequest.java

@@ -0,0 +1,47 @@
+package com.usky.sas.service.vo;
+
+import lombok.Data;
+
+/**
+ * 批量新增设备请求
+ */
+@Data
+public class DeviceBatchAddRequest {
+
+    /** 设备编号(必填) */
+    private String deviceId;
+
+    /** 设备类型(必填,对应 SystemTypeCodeEnum.code) */
+    private Integer deviceType;
+
+    /** 设备 IP(必填) */
+    private String ipAddr;
+
+    /** 设备端口(必填) */
+    private Integer port;
+
+    /** 通道数量(必填,>=1) */
+    private Integer channels;
+
+    /** 登录用户名 */
+    private String username;
+
+    /** 登录密码 */
+    private String password;
+
+    /** 安装位置 */
+    private String address;
+
+    /** 部位编码(houseCode) */
+    private String houseCode;
+
+    /** 视频设备类型:如 1-IPC、2-NVR 等(预留字段) */
+    private Integer videoDeviceType;
+
+    /** 视频协议:如 1-ONVIF、2-海康、3-大华 等(预留字段) */
+    private Integer videoProtocol;
+
+    /** 备注 */
+    private String note;
+}
+