Kaynağa Gözat

连油告警相关曲线

Zimo 21 saat önce
ebeveyn
işleme
2fe8f42da3
15 değiştirilmiş dosya ile 970 ekleme ve 0 silme
  1. 14 0
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/number/NumberUtils.java
  2. 98 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/alarm/IotAlarmRecordController.java
  3. 41 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/alarm/vo/IotAlarmRecordPageReqVO.java
  4. 47 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/alarm/vo/IotAlarmRecordRespVO.java
  5. 32 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/alarm/vo/IotAlarmRecordSaveReqVO.java
  6. 27 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/alarm/vo/WsTdPropertyVO.java
  7. 55 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/dal/dataobject/alarm/IotAlarmRecordDO.java
  8. 42 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/dal/mysql/alarm/IotAlarmRecordMapper.java
  9. 57 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/service/alarm/IotAlarmRecordService.java
  10. 76 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/service/alarm/IotAlarmRecordServiceImpl.java
  11. 218 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/websocket/DynamicWsClientHandler.java
  12. 28 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/websocket/WsAddressService.java
  13. 18 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/websocket/WsClientConfig.java
  14. 160 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/websocket/WsClientManager.java
  15. 57 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/websocket/WssSslConfigUtil.java

+ 14 - 0
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/number/NumberUtils.java

@@ -112,4 +112,18 @@ public class NumberUtils {
             return numberStr;
         }
     }
+    public static boolean isConvertibleToDouble(String str) {
+        // 空值、空字符串直接返回false
+        if (str == null || str.trim().isEmpty()) {
+            return false;
+        }
+        try {
+            // 尝试解析为double,解析成功则返回true
+            Double.parseDouble(str.trim());
+            return true;
+        } catch (NumberFormatException e) {
+            // 解析失败(非数字格式)返回false
+            return false;
+        }
+    }
 }

+ 98 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/alarm/IotAlarmRecordController.java

@@ -0,0 +1,98 @@
+package cn.iocoder.yudao.module.pms.controller.admin.alarm;
+
+import cn.iocoder.yudao.module.pms.controller.admin.alarm.vo.IotAlarmRecordPageReqVO;
+import cn.iocoder.yudao.module.pms.controller.admin.alarm.vo.IotAlarmRecordRespVO;
+import cn.iocoder.yudao.module.pms.controller.admin.alarm.vo.IotAlarmRecordSaveReqVO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.alarm.IotAlarmRecordDO;
+import cn.iocoder.yudao.module.pms.service.alarm.IotAlarmRecordService;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.security.access.prepost.PreAuthorize;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.Operation;
+
+import java.util.*;
+import java.io.IOException;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+
+import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletResponse;
+import javax.validation.Valid;
+
+import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.*;
+
+
+@Tag(name = "管理后台 - 运行监控告警记录")
+@RestController
+@RequestMapping("/rq/iot-alarm-record")
+@Validated
+public class IotAlarmRecordController {
+
+    @Resource
+    private IotAlarmRecordService iotAlarmRecordService;
+
+    @PostMapping("/create")
+    @Operation(summary = "创建运行监控告警记录")
+    @PreAuthorize("@ss.hasPermission('rq:iot-alarm-record:create')")
+    public CommonResult<Long> createIotAlarmRecord(@Valid @RequestBody IotAlarmRecordSaveReqVO createReqVO) {
+        return success(iotAlarmRecordService.createIotAlarmRecord(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "更新运行监控告警记录")
+    @PreAuthorize("@ss.hasPermission('rq:iot-alarm-record:update')")
+    public CommonResult<Boolean> updateIotAlarmRecord(@Valid @RequestBody IotAlarmRecordSaveReqVO updateReqVO) {
+        iotAlarmRecordService.updateIotAlarmRecord(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "删除运行监控告警记录")
+    @Parameter(name = "id", description = "编号", required = true)
+    @PreAuthorize("@ss.hasPermission('rq:iot-alarm-record:delete')")
+    public CommonResult<Boolean> deleteIotAlarmRecord(@RequestParam("id") Long id) {
+        iotAlarmRecordService.deleteIotAlarmRecord(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "获得运行监控告警记录")
+    @Parameter(name = "id", description = "编号", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('rq:iot-alarm-record:query')")
+    public CommonResult<IotAlarmRecordRespVO> getIotAlarmRecord(@RequestParam("id") Long id) {
+        IotAlarmRecordDO iotAlarmRecord = iotAlarmRecordService.getIotAlarmRecord(id);
+        return success(BeanUtils.toBean(iotAlarmRecord, IotAlarmRecordRespVO.class));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "获得运行监控告警记录分页")
+    @PreAuthorize("@ss.hasPermission('rq:iot-alarm-record:query')")
+    public CommonResult<PageResult<IotAlarmRecordRespVO>> getIotAlarmRecordPage(@Valid IotAlarmRecordPageReqVO pageReqVO) {
+        PageResult<IotAlarmRecordDO> pageResult = iotAlarmRecordService.getIotAlarmRecordPage(pageReqVO);
+        return success(BeanUtils.toBean(pageResult, IotAlarmRecordRespVO.class));
+    }
+
+    @GetMapping("/export-excel")
+    @Operation(summary = "导出运行监控告警记录 Excel")
+    @PreAuthorize("@ss.hasPermission('rq:iot-alarm-record:export')")
+    @ApiAccessLog(operateType = EXPORT)
+    public void exportIotAlarmRecordExcel(@Valid IotAlarmRecordPageReqVO pageReqVO,
+              HttpServletResponse response) throws IOException {
+        pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
+        List<IotAlarmRecordDO> list = iotAlarmRecordService.getIotAlarmRecordPage(pageReqVO).getList();
+        // 导出 Excel
+        ExcelUtils.write(response, "运行监控告警记录.xls", "数据", IotAlarmRecordRespVO.class,
+                        BeanUtils.toBean(list, IotAlarmRecordRespVO.class));
+    }
+
+}

+ 41 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/alarm/vo/IotAlarmRecordPageReqVO.java

@@ -0,0 +1,41 @@
+package cn.iocoder.yudao.module.pms.controller.admin.alarm.vo;
+
+import lombok.*;
+import java.util.*;
+import io.swagger.v3.oas.annotations.media.Schema;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import org.springframework.format.annotation.DateTimeFormat;
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "管理后台 - 运行监控告警记录分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class IotAlarmRecordPageReqVO extends PageParam {
+
+    @Schema(description = "告警类型", example = "2")
+    private String type;
+
+    @Schema(description = "设备编号")
+    private String deviceCode;
+
+    @Schema(description = "设备属性")
+    private String deviceProperty;
+
+    @Schema(description = "告警时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private String[] alarmTime;
+
+    @Schema(description = "告警数值")
+    private String alarmValue;
+
+    @Schema(description = "备注", example = "随便")
+    private String remark;
+
+    @Schema(description = "创建时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime[] createTime;
+
+}

+ 47 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/alarm/vo/IotAlarmRecordRespVO.java

@@ -0,0 +1,47 @@
+package cn.iocoder.yudao.module.pms.controller.admin.alarm.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+import java.util.*;
+import org.springframework.format.annotation.DateTimeFormat;
+import java.time.LocalDateTime;
+import com.alibaba.excel.annotation.*;
+
+@Schema(description = "管理后台 - 运行监控告警记录 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class IotAlarmRecordRespVO {
+
+    @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "19813")
+    @ExcelProperty("主键")
+    private Long id;
+
+    @Schema(description = "告警类型", example = "2")
+    @ExcelProperty("告警类型")
+    private String type;
+
+    @Schema(description = "设备编号")
+    @ExcelProperty("设备编号")
+    private String deviceCode;
+
+    @Schema(description = "设备属性")
+    @ExcelProperty("设备属性")
+    private String deviceProperty;
+
+    @Schema(description = "告警时间")
+    @ExcelProperty("告警时间")
+    private String alarmTime;
+
+    @Schema(description = "告警数值")
+    @ExcelProperty("告警数值")
+    private String alarmValue;
+
+    @Schema(description = "备注", example = "随便")
+    @ExcelProperty("备注")
+    private String remark;
+
+    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("创建时间")
+    private LocalDateTime createTime;
+
+}

+ 32 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/alarm/vo/IotAlarmRecordSaveReqVO.java

@@ -0,0 +1,32 @@
+package cn.iocoder.yudao.module.pms.controller.admin.alarm.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+import java.util.*;
+
+@Schema(description = "管理后台 - 运行监控告警记录新增/修改 Request VO")
+@Data
+public class IotAlarmRecordSaveReqVO {
+
+    @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "19813")
+    private Long id;
+
+    @Schema(description = "告警类型", example = "2")
+    private String type;
+
+    @Schema(description = "设备编号")
+    private String deviceCode;
+
+    @Schema(description = "设备属性")
+    private String deviceProperty;
+
+    @Schema(description = "告警时间")
+    private String alarmTime;
+
+    @Schema(description = "告警数值")
+    private String alarmValue;
+
+    @Schema(description = "备注", example = "随便")
+    private String remark;
+
+}

+ 27 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/alarm/vo/WsTdPropertyVO.java

@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.pms.controller.admin.alarm.vo;
+
+import lombok.Data;
+
+import java.util.Date;
+
+@Data
+public class WsTdPropertyVO {
+    private long batchId;
+    private Date createTime;
+    private String identity;
+    private int isMonitor;
+    private int logType;
+    private String logValue;
+    private int mode;
+    private String modelName;
+//    private Params params;
+    private Date readTime;
+    private String remark;
+    private String serialNumber;
+    private int tenantId;
+    private String tenantName;
+    private int total;
+    private long tsns;
+    private int userId;
+    private String userName;
+}

+ 55 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/dal/dataobject/alarm/IotAlarmRecordDO.java

@@ -0,0 +1,55 @@
+package cn.iocoder.yudao.module.pms.dal.dataobject.alarm;
+
+import lombok.*;
+import java.util.*;
+import java.time.LocalDateTime;
+import java.time.LocalDateTime;
+import com.baomidou.mybatisplus.annotation.*;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+
+/**
+ * 运行监控告警记录 DO
+ *
+ * @author 超级管理员
+ */
+@TableName("rq_iot_alarm_record")
+@KeySequence("rq_iot_alarm_record_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class IotAlarmRecordDO extends BaseDO {
+
+    /**
+     * 主键
+     */
+    @TableId
+    private Long id;
+    /**
+     * 告警类型
+     */
+    private String type;
+    /**
+     * 设备编号
+     */
+    private String deviceCode;
+    /**
+     * 设备属性
+     */
+    private String deviceProperty;
+    /**
+     * 告警时间
+     */
+    private String alarmTime;
+    /**
+     * 告警数值
+     */
+    private String alarmValue;
+    /**
+     * 备注
+     */
+    private String remark;
+
+}

+ 42 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/dal/mysql/alarm/IotAlarmRecordMapper.java

@@ -0,0 +1,42 @@
+package cn.iocoder.yudao.module.pms.dal.mysql.alarm;
+
+import java.util.*;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.module.pms.controller.admin.alarm.vo.IotAlarmRecordPageReqVO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.alarm.IotAlarmRecordDO;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 运行监控告警记录 Mapper
+ *
+ * @author 超级管理员
+ */
+@Mapper
+public interface IotAlarmRecordMapper extends BaseMapperX<IotAlarmRecordDO> {
+
+    default PageResult<IotAlarmRecordDO> selectPage(IotAlarmRecordPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<IotAlarmRecordDO>()
+                .eqIfPresent(IotAlarmRecordDO::getType, reqVO.getType())
+                .eqIfPresent(IotAlarmRecordDO::getDeviceCode, reqVO.getDeviceCode())
+                .eqIfPresent(IotAlarmRecordDO::getDeviceProperty, reqVO.getDeviceProperty())
+                .betweenIfPresent(IotAlarmRecordDO::getAlarmTime, reqVO.getAlarmTime())
+                .eqIfPresent(IotAlarmRecordDO::getAlarmValue, reqVO.getAlarmValue())
+                .eqIfPresent(IotAlarmRecordDO::getRemark, reqVO.getRemark())
+                .betweenIfPresent(IotAlarmRecordDO::getCreateTime, reqVO.getCreateTime())
+                .orderByDesc(IotAlarmRecordDO::getId));
+    }
+    default List<IotAlarmRecordDO> selectListAlarm(IotAlarmRecordPageReqVO reqVO) {
+        return selectList(new LambdaQueryWrapperX<IotAlarmRecordDO>()
+                .eqIfPresent(IotAlarmRecordDO::getType, reqVO.getType())
+                .eqIfPresent(IotAlarmRecordDO::getDeviceCode, reqVO.getDeviceCode())
+                .eqIfPresent(IotAlarmRecordDO::getDeviceProperty, reqVO.getDeviceProperty())
+                .betweenIfPresent(IotAlarmRecordDO::getAlarmTime, reqVO.getAlarmTime())
+                .eqIfPresent(IotAlarmRecordDO::getAlarmValue, reqVO.getAlarmValue())
+                .eqIfPresent(IotAlarmRecordDO::getRemark, reqVO.getRemark())
+                .betweenIfPresent(IotAlarmRecordDO::getCreateTime, reqVO.getCreateTime())
+                .orderByDesc(IotAlarmRecordDO::getId));
+    }
+}

+ 57 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/service/alarm/IotAlarmRecordService.java

@@ -0,0 +1,57 @@
+package cn.iocoder.yudao.module.pms.service.alarm;
+
+import java.util.*;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.module.pms.controller.admin.alarm.vo.IotAlarmRecordPageReqVO;
+import cn.iocoder.yudao.module.pms.controller.admin.alarm.vo.IotAlarmRecordSaveReqVO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.alarm.IotAlarmRecordDO;
+
+import javax.validation.Valid;
+
+/**
+ * 运行监控告警记录 Service 接口
+ *
+ * @author 超级管理员
+ */
+public interface IotAlarmRecordService {
+
+    /**
+     * 创建运行监控告警记录
+     *
+     * @param createReqVO 创建信息
+     * @return 编号
+     */
+    Long createIotAlarmRecord(@Valid IotAlarmRecordSaveReqVO createReqVO);
+
+    /**
+     * 更新运行监控告警记录
+     *
+     * @param updateReqVO 更新信息
+     */
+    void updateIotAlarmRecord(@Valid IotAlarmRecordSaveReqVO updateReqVO);
+
+    /**
+     * 删除运行监控告警记录
+     *
+     * @param id 编号
+     */
+    void deleteIotAlarmRecord(Long id);
+
+    /**
+     * 获得运行监控告警记录
+     *
+     * @param id 编号
+     * @return 运行监控告警记录
+     */
+    IotAlarmRecordDO getIotAlarmRecord(Long id);
+
+    /**
+     * 获得运行监控告警记录分页
+     *
+     * @param pageReqVO 分页查询
+     * @return 运行监控告警记录分页
+     */
+    PageResult<IotAlarmRecordDO> getIotAlarmRecordPage(IotAlarmRecordPageReqVO pageReqVO);
+
+}

+ 76 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/service/alarm/IotAlarmRecordServiceImpl.java

@@ -0,0 +1,76 @@
+package cn.iocoder.yudao.module.pms.service.alarm;
+
+import cn.iocoder.yudao.framework.common.exception.ErrorCode;
+import cn.iocoder.yudao.module.pms.controller.admin.alarm.vo.IotAlarmRecordPageReqVO;
+import cn.iocoder.yudao.module.pms.controller.admin.alarm.vo.IotAlarmRecordSaveReqVO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.alarm.IotAlarmRecordDO;
+import cn.iocoder.yudao.module.pms.dal.mysql.alarm.IotAlarmRecordMapper;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.*;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+
+
+import javax.annotation.Resource;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+
+/**
+ * 运行监控告警记录 Service 实现类
+ *
+ * @author 超级管理员
+ */
+@Service
+@Validated
+public class IotAlarmRecordServiceImpl implements IotAlarmRecordService {
+
+    @Resource
+    private IotAlarmRecordMapper iotAlarmRecordMapper;
+
+    @Override
+    public Long createIotAlarmRecord(IotAlarmRecordSaveReqVO createReqVO) {
+        // 插入
+        IotAlarmRecordDO iotAlarmRecord = BeanUtils.toBean(createReqVO, IotAlarmRecordDO.class);
+        iotAlarmRecordMapper.insert(iotAlarmRecord);
+        // 返回
+        return iotAlarmRecord.getId();
+    }
+
+    @Override
+    public void updateIotAlarmRecord(IotAlarmRecordSaveReqVO updateReqVO) {
+        // 校验存在
+        validateIotAlarmRecordExists(updateReqVO.getId());
+        // 更新
+        IotAlarmRecordDO updateObj = BeanUtils.toBean(updateReqVO, IotAlarmRecordDO.class);
+        iotAlarmRecordMapper.updateById(updateObj);
+    }
+
+    @Override
+    public void deleteIotAlarmRecord(Long id) {
+        // 校验存在
+        validateIotAlarmRecordExists(id);
+        // 删除
+        iotAlarmRecordMapper.deleteById(id);
+    }
+
+    private void validateIotAlarmRecordExists(Long id) {
+        if (iotAlarmRecordMapper.selectById(id) == null) {
+            throw exception(new ErrorCode(1,"不存在"));
+        }
+    }
+
+    @Override
+    public IotAlarmRecordDO getIotAlarmRecord(Long id) {
+        return iotAlarmRecordMapper.selectById(id);
+    }
+
+    @Override
+    public PageResult<IotAlarmRecordDO> getIotAlarmRecordPage(IotAlarmRecordPageReqVO pageReqVO) {
+        return iotAlarmRecordMapper.selectPage(pageReqVO);
+    }
+
+}

+ 218 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/websocket/DynamicWsClientHandler.java

@@ -0,0 +1,218 @@
+package cn.iocoder.yudao.module.pms.websocket;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.date.LocalDateTimeUtil;
+import cn.hutool.extra.spring.SpringUtil;
+import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
+import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
+import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
+import cn.iocoder.yudao.module.pms.constant.PmsConstants;
+import cn.iocoder.yudao.module.pms.controller.admin.alarm.vo.IotAlarmRecordPageReqVO;
+import cn.iocoder.yudao.module.pms.controller.admin.alarm.vo.WsTdPropertyVO;
+import cn.iocoder.yudao.module.pms.controller.admin.iotdeviceperson.vo.IotDevicePersonPageReqVO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.IotDeviceDO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.alarm.IotAlarmRecordDO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.alarm.IotAlarmSettingDO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.iotdeviceperson.IotDevicePersonDO;
+import cn.iocoder.yudao.module.pms.dal.mysql.IotDeviceMapper;
+import cn.iocoder.yudao.module.pms.dal.mysql.alarm.IotAlarmRecordMapper;
+import cn.iocoder.yudao.module.pms.dal.mysql.alarm.IotAlarmSettingMapper;
+import cn.iocoder.yudao.module.pms.message.PmsMessage;
+import cn.iocoder.yudao.module.pms.service.iotdeviceperson.IotDevicePersonService;
+import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
+import com.alibaba.fastjson.JSON;
+import com.google.common.collect.ImmutableList;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.socket.CloseStatus;
+import org.springframework.web.socket.TextMessage;
+import org.springframework.web.socket.WebSocketSession;
+import org.springframework.web.socket.handler.TextWebSocketHandler;
+
+import java.time.LocalDateTime;
+import java.time.temporal.ChronoUnit;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * 简化版 WS 客户端处理器(仅接收消息,不主动发送)
+ */
+@Slf4j
+public class DynamicWsClientHandler extends TextWebSocketHandler {
+
+    /** 当前连接的配置(含 clientId 和 wsUrl) */
+    private final WsClientConfig config;
+    /** 连接管理器(用于重连、移除连接) */
+    private final WsClientManager manager;
+    private final IotAlarmRecordMapper iotAlarmRecordMapper;
+    private final IotAlarmSettingMapper iotAlarmSettingMapper;
+    private final IotDeviceMapper iotDeviceMapper;
+    private final IotDevicePersonService iotDevicePersonService;
+    private final AdminUserApi adminUserApi;
+    private final PmsMessage pmsMessage;
+    public DynamicWsClientHandler(WsClientConfig config, WsClientManager manager) {
+        this.config = config;
+        this.manager = manager;
+        this.iotAlarmRecordMapper = SpringUtil.getBean(IotAlarmRecordMapper.class);
+        this.iotAlarmSettingMapper = SpringUtil.getBean(IotAlarmSettingMapper.class);
+        this.iotDeviceMapper = SpringUtil.getBean(IotDeviceMapper.class);
+        this.iotDevicePersonService = SpringUtil.getBean(IotDevicePersonService.class);
+        this.adminUserApi = SpringUtil.getBean(AdminUserApi.class);
+        this.pmsMessage = SpringUtil.getBean(PmsMessage.class);
+    }
+
+    /**
+     * 连接成功建立(仅记录日志,不发送初始化消息)
+     */
+    @Override
+    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
+        // 将会话绑定到当前客户端ID,存入管理器
+        manager.bindSession(config.getClientId(), session);
+        // 日志标注 Nginx 代理的 WSS 连接
+        log.info("✅ [{}] 成功连接Nginx代理的WSS服务:{},开始接收消息", config.getClientId(), config.getWsUrl());
+    }
+
+    /**
+     * 核心逻辑:接收服务端推送的文本消息
+     */
+    @Override
+    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
+        String payload = message.getPayload();
+        String clientId = config.getClientId();
+        log.info("📩 [{}] 收到Nginx代理WSS推送消息:{}", clientId, payload);
+
+        // 核心:处理接收到的消息(芋道框架业务逻辑)
+        handleReceivedMessage(clientId, payload);
+    }
+
+    /**
+     * 连接关闭(仅处理重连)
+     */
+    @Override
+    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
+        log.warn("❌ [{}] Nginx代理WSS连接关闭,状态码:{},原因:{}",
+                config.getClientId(), status.getCode(), status.getReason());
+        // 从管理器移除失效会话
+        manager.removeSession(config.getClientId());
+        // 触发重连,保证消息接收不中断
+        manager.reconnect(config);
+    }
+
+    /**
+     * 连接异常(仅处理重连)
+     */
+    @Override
+    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
+        log.error("❌ [{}] Nginx代理WSS连接异常", config.getClientId(), exception);
+        // 关闭异常会话
+        if (session.isOpen()) {
+            session.close(CloseStatus.SERVER_ERROR);
+        }
+        // 从管理器移除失效会话
+        manager.removeSession(config.getClientId());
+        // 触发重连
+        manager.reconnect(config);
+    }
+
+
+    /**
+     * 处理接收到的消息(核心业务逻辑,适配芋道框架)
+     * 可直接注入芋道的 Service/Mapper 处理消息
+     */
+    @TenantIgnore
+    private void handleReceivedMessage(String clientId, String message) {
+        try {
+            List<WsTdPropertyVO> wsTdPropertyVOS = JSON.parseArray(message, WsTdPropertyVO.class);
+            //todo websocket
+            TenantUtils.executeIgnore(() ->{
+                List<IotDeviceDO> iotDeviceDOS = iotDeviceMapper.selectByCodeIn(ImmutableList.of(clientId));
+                if (CollUtil.isNotEmpty(iotDeviceDOS)) {
+                    IotDeviceDO iotDeviceDO = iotDeviceDOS.get(0);
+                    List<IotAlarmSettingDO> alarms = iotAlarmSettingMapper.selectList("device_id", iotDeviceDO.getId());
+                    if (CollUtil.isNotEmpty(alarms)) {//设备赋值了
+                        wsTdPropertyVOS.forEach(wsTdPropertyVO -> {
+                           alarms.stream().filter(e -> e.getPropertyCode().equals(wsTdPropertyVO.getIdentity())).findFirst().ifPresent(alarm -> {
+                               if (NumberUtils.isConvertibleToDouble(wsTdPropertyVO.getLogValue())&&NumberUtils.isConvertibleToDouble(alarm.getMaxValue())&&
+                                    NumberUtils.isConvertibleToDouble(alarm.getMinValue())){//如果可以被转为数值
+                                   if (Double.parseDouble(wsTdPropertyVO.getLogValue())>Double.parseDouble(alarm.getMaxValue())||
+                                        Double.parseDouble(wsTdPropertyVO.getLogValue())<Double.parseDouble(alarm.getMinValue())) {//大于最大值,小于最小值
+                                       //查找近半小时的该设备该属性是否有过告警
+                                       IotAlarmRecordPageReqVO pageReqVO = new IotAlarmRecordPageReqVO();
+                                       pageReqVO.setDeviceCode(clientId);
+                                       pageReqVO.setDeviceProperty(wsTdPropertyVO.getIdentity());
+                                       LocalDateTime offset = LocalDateTimeUtil.offset(LocalDateTime.now(), -30L, ChronoUnit.MINUTES);
+                                       LocalDateTime[] createTime = new LocalDateTime[]{offset, LocalDateTime.now()};
+                                       pageReqVO.setCreateTime(createTime);
+                                       List<IotAlarmRecordDO> iotAlarmRecordDOS = iotAlarmRecordMapper.selectListAlarm(pageReqVO);
+                                       if (CollUtil.isEmpty(iotAlarmRecordDOS)) {//如果空的话报警并且插入
+                                           IotAlarmRecordDO iotAlarmRecordDO = new IotAlarmRecordDO();
+                                           iotAlarmRecordDO.setDeviceCode(clientId);
+                                           iotAlarmRecordDO.setDeviceProperty(wsTdPropertyVO.getIdentity());
+                                           iotAlarmRecordDO.setAlarmTime(DateUtil.now());
+                                           iotAlarmRecordDO.setAlarmValue(wsTdPropertyVO.getLogValue());
+                                           iotAlarmRecordDO.setDeleted(false);
+                                           iotAlarmRecordMapper.insert(iotAlarmRecordDO);
+                                           //发送告警消息通知
+                                           IotDevicePersonPageReqVO personPageReqVO = new IotDevicePersonPageReqVO();
+                                           personPageReqVO.setDeviceIds(ImmutableList.of(iotDeviceDO.getId()));
+                                           List<IotDevicePersonDO> persons = iotDevicePersonService.getPersonsByDeviceIds(personPageReqVO);
+                                           if (CollUtil.isNotEmpty(persons)) {
+                                               AdminUserRespDTO user = adminUserApi.getUser(persons.get(0).getPersonId());
+                                               if (Objects.nonNull(user)) {
+                                                   pmsMessage.sendMessage(iotDeviceDO.getId(), iotDeviceDO.getDeviceCode()+iotDeviceDO.getDeviceName()+","+wsTdPropertyVO.getModelName(), PmsConstants.ALARM_MESSAGE, user.getId(), user.getMobile());
+                                               }
+                                           }
+                                       }
+                                   }
+                               }
+                           });
+                        });
+                    } else {//设备没赋值,就去找分类的max及min值
+                        List<IotAlarmSettingDO> flAlarms = iotAlarmSettingMapper.selectList("classify_id", iotDeviceDO.getAssetClass());
+                        if (CollUtil.isNotEmpty(flAlarms)) {
+                            wsTdPropertyVOS.forEach(wsTdPropertyVO -> {
+                                flAlarms.stream().filter(e -> e.getPropertyCode().equals(wsTdPropertyVO.getIdentity())).findFirst().ifPresent(alarm -> {
+                                    if (NumberUtils.isConvertibleToDouble(wsTdPropertyVO.getLogValue())&&NumberUtils.isConvertibleToDouble(alarm.getMaxValue())&&
+                                            NumberUtils.isConvertibleToDouble(alarm.getMinValue())){//如果可以被转为数值
+                                        if (Double.parseDouble(wsTdPropertyVO.getLogValue())>Double.parseDouble(alarm.getMaxValue())||
+                                                Double.parseDouble(wsTdPropertyVO.getLogValue())<Double.parseDouble(alarm.getMinValue())) {//大于最大值,小于最小值
+                                            //查找近半小时的该设备该属性是否有过告警
+                                            IotAlarmRecordPageReqVO pageReqVO = new IotAlarmRecordPageReqVO();
+                                            pageReqVO.setDeviceCode(clientId);
+                                            pageReqVO.setDeviceProperty(wsTdPropertyVO.getIdentity());
+                                            LocalDateTime offset = LocalDateTimeUtil.offset(LocalDateTime.now(), 30L, ChronoUnit.MINUTES);
+                                            LocalDateTime[] createTime = new LocalDateTime[]{offset, LocalDateTime.now()};
+                                            pageReqVO.setCreateTime(createTime);
+                                            List<IotAlarmRecordDO> iotAlarmRecordDOS = iotAlarmRecordMapper.selectListAlarm(pageReqVO);
+                                            if (CollUtil.isEmpty(iotAlarmRecordDOS)) {//如果空的话报警并且插入
+                                                IotAlarmRecordDO iotAlarmRecordDO = new IotAlarmRecordDO();
+                                                iotAlarmRecordDO.setDeviceCode(clientId);
+                                                iotAlarmRecordDO.setDeviceProperty(wsTdPropertyVO.getIdentity());
+                                                iotAlarmRecordDO.setAlarmTime(DateUtil.now());
+                                                iotAlarmRecordDO.setAlarmValue(wsTdPropertyVO.getLogValue());
+                                                iotAlarmRecordDO.setDeleted(false);
+                                                iotAlarmRecordMapper.insert(iotAlarmRecordDO);
+                                                //发送告警消息通知
+                                                IotDevicePersonPageReqVO personPageReqVO = new IotDevicePersonPageReqVO();
+                                                personPageReqVO.setDeviceIds(ImmutableList.of(iotDeviceDO.getId()));
+                                                List<IotDevicePersonDO> persons = iotDevicePersonService.getPersonsByDeviceIds(personPageReqVO);
+                                                if (CollUtil.isNotEmpty(persons)) {
+                                                    AdminUserRespDTO user = adminUserApi.getUser(persons.get(0).getPersonId());
+                                                    if (Objects.nonNull(user)) {
+                                                        pmsMessage.sendMessage(iotDeviceDO.getId(), iotDeviceDO.getDeviceCode()+iotDeviceDO.getDeviceName()+","+wsTdPropertyVO.getModelName(), PmsConstants.ALARM_MESSAGE, user.getId(), user.getMobile());
+                                                    }
+                                                }
+                                            }
+                                        }
+                                    }
+                                });
+                            });
+                        }
+                    }
+                }});
+        } catch (Exception e) {
+            log.error("❌ [{}] 处理消息失败", clientId, e);
+        }
+    }
+}

+ 28 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/websocket/WsAddressService.java

@@ -0,0 +1,28 @@
+package cn.iocoder.yudao.module.pms.websocket;
+
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 动态获取 WS 连接地址的服务(适配 Nginx 代理的 WSS 地址)
+ */
+@Service
+public class WsAddressService {
+
+    /**
+     * 从接口/数据库查询所有需要连接的 WSS 标识和地址(Nginx 代理地址)
+     * 实际场景可替换为 Feign 调用、Mapper 查询等
+     */
+    public List<WsClientConfig> listAllWsConfigs() {
+        // 核心:使用 WSS 协议 + Nginx 代理域名(对应 https://iot.deepoil.cc/ws/YF123)
+        String nginxWssBaseUrl = "wss://iot.deepoil.cc/ws/";
+        // 模拟查询结果:实际可从数据库/远程接口获取多个动态标识
+        List<WsClientConfig> configs = new ArrayList<>();
+        configs.add(new WsClientConfig("YF6660355", nginxWssBaseUrl + "YF6660355"));
+//        configs.add(new WsClientConfig("YF456", nginxWssBaseUrl + "YF456"));
+//        configs.add(new WsClientConfig("YF789", nginxWssBaseUrl + "YF789"));
+        return configs;
+    }
+}

+ 18 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/websocket/WsClientConfig.java

@@ -0,0 +1,18 @@
+package cn.iocoder.yudao.module.pms.websocket;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * WS 连接配置实体(存储动态获取的连接信息)
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class WsClientConfig {
+    /** 唯一标识(如 YF123、YF456) */
+    private String clientId;
+    /** 完整 WS 连接地址 */
+    private String wsUrl;
+}

+ 160 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/websocket/WsClientManager.java

@@ -0,0 +1,160 @@
+package cn.iocoder.yudao.module.pms.websocket;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import org.springframework.web.socket.WebSocketSession;
+import org.springframework.web.socket.client.standard.StandardWebSocketClient;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+import javax.annotation.Resource;
+import java.net.URI;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+/**
+ * 适配 Nginx 代理的 WS 客户端管理器(仅管理连接,不处理发送)
+ */
+@Component
+@Slf4j
+public class WsClientManager {
+
+    /** 存储客户端ID -> WS会话的映射(线程安全) */
+    private final Map<String, WebSocketSession> sessionMap = new ConcurrentHashMap<>();
+    /** 核心线程池(处理连接、重连) */
+    private final ExecutorService executor = Executors.newFixedThreadPool(10); // 适配多连接
+
+    @Resource
+    private WsAddressService wsAddressService;
+
+    /**
+     * 项目启动时初始化所有动态连接(自动开始接收消息)
+     */
+    @PostConstruct
+    public void initAllConnections() {
+        executor.submit(this::connectAll);
+    }
+
+    /**
+     * 批量连接所有动态获取的 WSS 地址(Nginx 代理)
+     */
+    private void connectAll() {
+        // 1. 从接口/数据库查询所有需要连接的配置
+        List<WsClientConfig> configs = wsAddressService.listAllWsConfigs();
+        if (configs.isEmpty()) {
+            log.warn("⚠️ 未查询到任何Nginx代理WSS连接配置,无需建立连接");
+            return;
+        }
+
+        // 2. 为每个配置创建独立连接(每个连接独立接收消息)
+        for (WsClientConfig config : configs) {
+            executor.submit(() -> connect(config));
+        }
+    }
+
+    /**
+     * 连接单个 WSS 地址(适配 Nginx 代理)
+     */
+    private void connect(WsClientConfig config) {
+        try {
+            String clientId = config.getClientId();
+            // 避免重复连接
+            if (sessionMap.containsKey(clientId) && sessionMap.get(clientId).isOpen()) {
+                log.info("⚠️ [{}] 已存在有效Nginx代理WSS连接,无需重复创建", clientId);
+                return;
+            }
+
+            // 核心调整1:创建支持 WSS 的客户端(适配 Nginx 代理)
+            StandardWebSocketClient wsClient = createWssEnabledClient();
+            // 核心调整2:解析 WSS 地址,确保路径正确(适配 Nginx 转发到 172.21.10.65:8080/ws/)
+            URI wssUri = URI.create(config.getWsUrl());
+            // 创建专属处理器(仅接收消息)
+            DynamicWsClientHandler handler = new DynamicWsClientHandler(config, this);
+
+            // 发起 WSS 连接(芋道框架原生能力 + SSL 配置)
+            Future<WebSocketSession> future = wsClient.doHandshake(handler, String.valueOf(wssUri));
+            // 阻塞等待连接结果(确保连接建立)
+            WebSocketSession session = future.get();
+            log.info("🔌 [{}] WSS连接请求已发送,路径:{},代理转发到:http://172.21.10.65:8080/ws/{}",
+                    clientId, wssUri.getPath(), clientId);
+        } catch (Exception e) {
+            log.error("🔌 [{}] 连接Nginx代理WSS失败", config.getClientId(), e);
+            // 触发重连,保证消息接收链路不中断
+            reconnect(config);
+        }
+    }
+
+    /**
+     * 创建支持 WSS 的客户端(配置 SSL 上下文,适配 Nginx 代理)
+     */
+    private StandardWebSocketClient createWssEnabledClient() {
+        StandardWebSocketClient wsClient = new StandardWebSocketClient();
+        // 核心:设置 SSL 上下文(解决 Nginx 证书校验问题)
+        wsClient.getUserProperties().put(
+                "javax.net.ssl.SSLContext",
+                WssSslConfigUtil.createIgnoreVerifySSLContext()
+        );
+        // 设置 SSL 参数(适配 SNI 扩展)
+        wsClient.getUserProperties().put(
+                "javax.net.ssl.SSLParameters",
+                WssSslConfigUtil.getSslParameters()
+        );
+        return wsClient;
+    }
+
+    /**
+     * 重连指定客户端(连接异常/关闭时自动触发)
+     */
+    public void reconnect(WsClientConfig config) {
+        executor.submit(() -> {
+            try {
+                // 重连间隔(5秒),避免频繁重试
+                Thread.sleep(5000);
+                log.info("🔄 [{}] 尝试重新连接Nginx代理WSS...", config.getClientId());
+                connect(config);
+            } catch (InterruptedException e) {
+                log.error("[{}] 重连线程被中断", config.getClientId(), e);
+                Thread.currentThread().interrupt();
+            }
+        });
+    }
+
+    /**
+     * 绑定客户端ID和会话(供处理器调用)
+     */
+    public void bindSession(String clientId, WebSocketSession session) {
+        sessionMap.put(clientId, session);
+    }
+
+    /**
+     * 移除指定客户端的会话(供处理器调用)
+     */
+    public void removeSession(String clientId) {
+        sessionMap.remove(clientId);
+    }
+
+    /**
+     * 项目关闭时优雅释放资源
+     */
+    @PreDestroy
+    public void destroy() {
+        // 关闭所有WS会话
+        sessionMap.forEach((clientId, session) -> {
+            if (session.isOpen()) {
+                try {
+                    session.close();
+                    log.info("🔚 [{}] Nginx代理WSS连接已关闭", clientId);
+                } catch (Exception e) {
+                    log.error("🔚 [{}] 关闭Nginx代理WSS连接失败", clientId, e);
+                }
+            }
+        });
+        // 关闭线程池
+        executor.shutdown();
+        log.info("🔚 所有Nginx代理WSS客户端资源已释放");
+    }
+}

+ 57 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/websocket/WssSslConfigUtil.java

@@ -0,0 +1,57 @@
+package cn.iocoder.yudao.module.pms.websocket;
+
+import lombok.extern.slf4j.Slf4j;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLParameters;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+import java.security.cert.X509Certificate;
+
+/**
+ * WSS 客户端 SSL 配置工具(适配 Nginx 代理的证书校验)
+ */
+@Slf4j
+public class WssSslConfigUtil {
+
+    /**
+     * 创建忽略证书校验的 SSL 上下文(适配 Nginx 自签名证书/内网证书场景)
+     * 生产环境建议替换为信任指定证书的配置
+     */
+    public static SSLContext createIgnoreVerifySSLContext() {
+        try {
+            // 构建信任管理器(忽略证书校验)
+            TrustManager[] trustAllCerts = new TrustManager[]{
+                    new X509TrustManager() {
+                        @Override
+                        public void checkClientTrusted(X509Certificate[] chain, String authType) {}
+
+                        @Override
+                        public void checkServerTrusted(X509Certificate[] chain, String authType) {}
+
+                        @Override
+                        public X509Certificate[] getAcceptedIssuers() {
+                            return new X509Certificate[0];
+                        }
+                    }
+            };
+            // 初始化 SSL 上下文
+            SSLContext sslContext = SSLContext.getInstance("TLS");
+            sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
+            return sslContext;
+        } catch (Exception e) {
+            log.error("创建 WSS SSL 上下文失败", e);
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * 获取 SSL 参数(适配 Nginx 代理的 SNI 扩展)
+     */
+    public static SSLParameters getSslParameters() {
+        SSLParameters sslParams = new SSLParameters();
+        // 启用 SNI(Server Name Indication),适配 Nginx 多域名代理场景
+        sslParams.setEndpointIdentificationAlgorithm("HTTPS");
+        return sslParams;
+    }
+}