baoshiwei
2025-06-05 eb54eea87c39f35b5d0476f146bfd29d7c7841be
feat(空调管理): 添加空调管理相关功能

- 新增空调管理、空调操作日志、空调定时任务等功能模块
- 实现了空调列表查询、空调信息获取、新增/修改/删除空调等功能- 添加了空调操作日志的查询、新增/删除等功能
已添加25个文件
已修改3个文件
3098 ■■■■■ 文件已修改
pom.xml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-admin/pom.xml 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-admin/src/main/java/com/zhitan/web/controller/airconditioner/AirConditionerController.java 100 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-admin/src/main/java/com/zhitan/web/controller/airconditioner/AirConditionerLogController.java 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-admin/src/main/java/com/zhitan/web/controller/airconditioner/AirConditionerScheduleController.java 99 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-airconditioner/pom.xml 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-airconditioner/src/main/java/com/zhitan/airconditioner/config/SchedulingConfig.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-airconditioner/src/main/java/com/zhitan/airconditioner/domain/AirConditioner.java 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-airconditioner/src/main/java/com/zhitan/airconditioner/domain/AirConditionerLog.java 148 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-airconditioner/src/main/java/com/zhitan/airconditioner/domain/AirConditionerSchedule.java 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-airconditioner/src/main/java/com/zhitan/airconditioner/mapper/AirConditionerLogMapper.java 72 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-airconditioner/src/main/java/com/zhitan/airconditioner/mapper/AirConditionerMapper.java 72 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-airconditioner/src/main/java/com/zhitan/airconditioner/mapper/AirConditionerScheduleMapper.java 99 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-airconditioner/src/main/java/com/zhitan/airconditioner/service/IAirConditionerLogService.java 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-airconditioner/src/main/java/com/zhitan/airconditioner/service/IAirConditionerScheduleService.java 83 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-airconditioner/src/main/java/com/zhitan/airconditioner/service/IAirConditionerService.java 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-airconditioner/src/main/java/com/zhitan/airconditioner/service/impl/AirConditionerLogServiceImpl.java 85 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-airconditioner/src/main/java/com/zhitan/airconditioner/service/impl/AirConditionerScheduleServiceImpl.java 506 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-airconditioner/src/main/java/com/zhitan/airconditioner/service/impl/AirConditionerServiceImpl.java 216 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-quartz/pom.xml 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-quartz/src/main/java/com/zhitan/quartz/task/AirConditionerTask.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-vue/src/api/airconditioner/airconditioner.js 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-vue/src/api/airconditioner/log.js 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-vue/src/api/airconditioner/schedule.js 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-vue/src/views/airconditioner/index.vue 76 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-vue/src/views/airconditioner/list.vue 409 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-vue/src/views/airconditioner/log.vue 166 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-vue/src/views/airconditioner/schedule.vue 434 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pom.xml
@@ -278,6 +278,7 @@
        <module>zhitan-quartz</module>
        <module>zhitan-generator</module>
        <module>zhitan-common</module>
        <module>zhitan-airconditioner</module>
    </modules>
    <profiles>
zhitan-admin/pom.xml
@@ -71,6 +71,12 @@
            <version>1.16.7</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>com.zhitan</groupId>
            <artifactId>zhitan-airconditioner</artifactId>
            <version>2.5.2</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>
    <build>
zhitan-admin/src/main/java/com/zhitan/web/controller/airconditioner/AirConditionerController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,100 @@
package com.zhitan.web.controller.airconditioner;
import java.util.List;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.zhitan.common.annotation.Log;
import com.zhitan.common.core.controller.BaseController;
import com.zhitan.common.core.domain.AjaxResult;
import com.zhitan.common.enums.BusinessType;
import com.zhitan.common.utils.poi.ExcelUtil;
import com.zhitan.airconditioner.domain.AirConditioner;
import com.zhitan.airconditioner.service.IAirConditionerService;
import com.zhitan.common.core.page.TableDataInfo;
/**
 * ç©ºè°ƒç®¡ç†Controller
 *
 * @author zhitan
 */
@RestController
@RequestMapping("/system/airconditioner")
public class AirConditionerController extends BaseController
{
    @Autowired
    private IAirConditionerService airConditionerService;
    /**
     * æŸ¥è¯¢ç©ºè°ƒåˆ—表
     */
    @PreAuthorize("@ss.hasPermi('system:airconditioner:list')")
    @GetMapping("/list")
    public TableDataInfo list(AirConditioner airConditioner)
    {
        startPage();
        List<AirConditioner> list = airConditionerService.selectAirConditionerList(airConditioner);
        return getDataTable(list);
    }
    /**
     * èŽ·å–ç©ºè°ƒè¯¦ç»†ä¿¡æ¯
     */
    @PreAuthorize("@ss.hasPermi('system:airconditioner:query')")
    @GetMapping(value = "/{id}")
    public AjaxResult getInfo(@PathVariable("id") Long id)
    {
        return success(airConditionerService.selectAirConditionerById(id));
    }
    /**
     * æ–°å¢žç©ºè°ƒ
     */
    @PreAuthorize("@ss.hasPermi('system:airconditioner:add')")
    @Log(title = "空调管理", businessType = BusinessType.INSERT)
    @PostMapping
    public AjaxResult add(@RequestBody AirConditioner airConditioner)
    {
        return toAjax(airConditionerService.insertAirConditioner(airConditioner));
    }
    /**
     * ä¿®æ”¹ç©ºè°ƒ
     */
    @PreAuthorize("@ss.hasPermi('system:airconditioner:edit')")
    @Log(title = "空调管理", businessType = BusinessType.UPDATE)
    @PutMapping
    public AjaxResult edit(@RequestBody AirConditioner airConditioner)
    {
        return toAjax(airConditionerService.updateAirConditioner(airConditioner));
    }
    /**
     * åˆ é™¤ç©ºè°ƒ
     */
    @PreAuthorize("@ss.hasPermi('system:airconditioner:remove')")
    @Log(title = "空调管理", businessType = BusinessType.DELETE)
    @DeleteMapping("/{ids}")
    public AjaxResult remove(@PathVariable Long[] ids)
    {
        return toAjax(airConditionerService.deleteAirConditionerByIds(ids));
    }
    /**
     * æŽ§åˆ¶ç©ºè°ƒå¼€å…³æœº
     */
    @PreAuthorize("@ss.hasPermi('system:airconditioner:control')")
    @Log(title = "空调控制", businessType = BusinessType.OTHER)
    @PostMapping("/control")
    public AjaxResult control(@RequestBody AirConditioner airConditioner)
    {
        return toAjax(airConditionerService.controlAirConditioner(airConditioner.getId(),  airConditioner.getMode(), "0"));
    }
}
zhitan-admin/src/main/java/com/zhitan/web/controller/airconditioner/AirConditionerLogController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,59 @@
package com.zhitan.web.controller.airconditioner;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.zhitan.common.annotation.Log;
import com.zhitan.common.core.controller.BaseController;
import com.zhitan.common.core.domain.AjaxResult;
import com.zhitan.common.core.page.TableDataInfo;
import com.zhitan.common.enums.BusinessType;
import com.zhitan.airconditioner.domain.AirConditionerLog;
import com.zhitan.airconditioner.service.IAirConditionerLogService;
/**
 * ç©ºè°ƒæ“ä½œæ—¥å¿—Controller
 *
 * @author zhitan
 */
@RestController
@RequestMapping("/system/airconditioner/log")
public class AirConditionerLogController extends BaseController {
    @Autowired
    private IAirConditionerLogService airConditionerLogService;
    /**
     * æŸ¥è¯¢ç©ºè°ƒæ“ä½œæ—¥å¿—列表
     */
    @PreAuthorize("@ss.hasPermi('airconditioner:log:list')")
    @GetMapping("/list")
    public TableDataInfo list(AirConditionerLog airConditionerLog) {
        startPage();
        List<AirConditionerLog> list = airConditionerLogService.selectAirConditionerLogList(airConditionerLog);
        return getDataTable(list);
    }
    /**
     * èŽ·å–ç©ºè°ƒæ“ä½œæ—¥å¿—è¯¦ç»†ä¿¡æ¯
     */
    @PreAuthorize("@ss.hasPermi('airconditioner:log:query')")
    @GetMapping(value = "/{id}")
    public AjaxResult getInfo(@PathVariable("id") Long id) {
        return success(airConditionerLogService.selectAirConditionerLogById(id));
    }
    /**
     * æ ¹æ®ç©ºè°ƒID查询操作日志列表
     */
    @PreAuthorize("@ss.hasPermi('airconditioner:log:list')")
    @GetMapping("/listByAirConditionerId/{airConditionerId}")
    public TableDataInfo listByAirConditionerId(@PathVariable("airConditionerId") Long airConditionerId) {
        startPage();
        List<AirConditionerLog> list = airConditionerLogService.selectAirConditionerLogByAirConditionerId(airConditionerId);
        return getDataTable(list);
    }
}
zhitan-admin/src/main/java/com/zhitan/web/controller/airconditioner/AirConditionerScheduleController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,99 @@
package com.zhitan.web.controller.airconditioner;
import java.util.List;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.zhitan.common.annotation.Log;
import com.zhitan.common.core.controller.BaseController;
import com.zhitan.common.core.domain.AjaxResult;
import com.zhitan.common.enums.BusinessType;
import com.zhitan.airconditioner.domain.AirConditionerSchedule;
import com.zhitan.airconditioner.service.IAirConditionerScheduleService;
import com.zhitan.common.core.page.TableDataInfo;
/**
 * ç©ºè°ƒå®šæ—¶ä»»åŠ¡Controller
 *
 * @author zhitan
 */
@RestController
@RequestMapping("/system/airconditioner/schedule")
public class AirConditionerScheduleController extends BaseController
{
    @Autowired
    private IAirConditionerScheduleService airConditionerScheduleService;
    /**
     * æŸ¥è¯¢ç©ºè°ƒå®šæ—¶ä»»åŠ¡åˆ—è¡¨
     */
    @PreAuthorize("@ss.hasPermi('system:airconditioner:schedule:list')")
    @GetMapping("/list")
    public TableDataInfo list(AirConditionerSchedule airConditionerSchedule)
    {
        startPage();
        List<AirConditionerSchedule> list = airConditionerScheduleService.selectAirConditionerScheduleList(airConditionerSchedule);
        return getDataTable(list);
    }
    /**
     * èŽ·å–ç©ºè°ƒå®šæ—¶ä»»åŠ¡è¯¦ç»†ä¿¡æ¯
     */
    @PreAuthorize("@ss.hasPermi('system:airconditioner:schedule:query')")
    @GetMapping(value = "/{id}")
    public AjaxResult getInfo(@PathVariable("id") Long id)
    {
        return success(airConditionerScheduleService.selectAirConditionerScheduleById(id));
    }
    /**
     * æ ¹æ®ç©ºè°ƒID查询定时任务列表
     */
    @PreAuthorize("@ss.hasPermi('system:airconditioner:schedule:list')")
    @GetMapping("/byAirConditioner/{airConditionerId}")
    public AjaxResult getByAirConditionerId(@PathVariable("airConditionerId") Long airConditionerId)
    {
        List<AirConditionerSchedule> list = airConditionerScheduleService.selectAirConditionerScheduleByAirConditionerId(airConditionerId);
        return success(list);
    }
    /**
     * æ–°å¢žç©ºè°ƒå®šæ—¶ä»»åŠ¡
     */
    @PreAuthorize("@ss.hasPermi('system:airconditioner:schedule:add')")
    @Log(title = "空调定时任务", businessType = BusinessType.INSERT)
    @PostMapping
    public AjaxResult add(@RequestBody AirConditionerSchedule airConditionerSchedule)
    {
        return toAjax(airConditionerScheduleService.insertAirConditionerSchedule(airConditionerSchedule));
    }
    /**
     * ä¿®æ”¹ç©ºè°ƒå®šæ—¶ä»»åŠ¡
     */
    @PreAuthorize("@ss.hasPermi('system:airconditioner:schedule:edit')")
    @Log(title = "空调定时任务", businessType = BusinessType.UPDATE)
    @PutMapping
    public AjaxResult edit(@RequestBody AirConditionerSchedule airConditionerSchedule)
    {
        return toAjax(airConditionerScheduleService.updateAirConditionerSchedule(airConditionerSchedule));
    }
    /**
     * åˆ é™¤ç©ºè°ƒå®šæ—¶ä»»åŠ¡
     */
    @PreAuthorize("@ss.hasPermi('system:airconditioner:schedule:remove')")
    @Log(title = "空调定时任务", businessType = BusinessType.DELETE)
    @DeleteMapping("/{ids}")
    public AjaxResult remove(@PathVariable Long[] ids)
    {
        return toAjax(airConditionerScheduleService.deleteAirConditionerScheduleByIds(ids));
    }
}
zhitan-airconditioner/pom.xml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>BaseAdminAPI</artifactId>
        <groupId>com.zhitan</groupId>
        <version>2.5.2</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>zhitan-airconditioner</artifactId>
    <description>
        ç©ºè°ƒæŽ§åˆ¶æ¨¡å—
    </description>
    <dependencies>
        <!-- é€šç”¨å·¥å…·-->
        <dependency>
            <groupId>com.zhitan</groupId>
            <artifactId>zhitan-common</artifactId>
        </dependency>
        <!-- framework框架-->
        <dependency>
            <groupId>com.zhitan</groupId>
            <artifactId>zhitan-framework</artifactId>
        </dependency>
        <!-- MQTT客户端 -->
        <dependency>
            <groupId>org.eclipse.paho</groupId>
            <artifactId>org.eclipse.paho.client.mqttv3</artifactId>
            <version>1.2.5</version>
        </dependency>
        <!-- SpringBoot集成MQTT -->
        <dependency>
            <groupId>org.springframework.integration</groupId>
            <artifactId>spring-integration-mqtt</artifactId>
        </dependency>
    </dependencies>
</project>
zhitan-airconditioner/src/main/java/com/zhitan/airconditioner/config/SchedulingConfig.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,33 @@
package com.zhitan.airconditioner.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
/**
 * å®šæ—¶ä»»åŠ¡è°ƒåº¦é…ç½®
 *
 * @author zhitan
 */
@Configuration
@EnableScheduling
public class SchedulingConfig {
    /**
     * é…ç½®TaskScheduler,用于动态调度任务
     * è®¾ç½®çº¿ç¨‹æ± å¤§å°ä¸º5,可以根据实际需求调整
     *
     * @return TaskScheduler实例
     */
    @Bean
    public TaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
        scheduler.setPoolSize(5);
        scheduler.setThreadNamePrefix("air-conditioner-scheduler-");
        scheduler.setWaitForTasksToCompleteOnShutdown(true);
        scheduler.setAwaitTerminationSeconds(60);
        return scheduler;
    }
}
zhitan-airconditioner/src/main/java/com/zhitan/airconditioner/domain/AirConditioner.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,39 @@
package com.zhitan.airconditioner.domain;
import com.zhitan.common.annotation.Excel;
import com.zhitan.common.core.domain.BaseEntity;
import lombok.Data;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
/**
 * ç©ºè°ƒæŽ§åˆ¶å™¨å¯¹è±¡ sys_air_conditioner
 *
 * @author zhitan
 */
@Data
public class AirConditioner extends BaseEntity
{
    private static final long serialVersionUID = 1L;
    /** ç©ºè°ƒID */
    private Long id;
    /** ç©ºè°ƒåç§° */
    @Excel(name = "空调名称")
    private String name;
    private String mode;
    /** æŽ§åˆ¶å™¨ID */
    @Excel(name = "控制器ID")
    private String controllerId;
    /** å®‰è£…位置 */
    @Excel(name = "安装位置")
    private String location;
    /** çŠ¶æ€ï¼ˆ0正常 1停用) */
    @Excel(name = "状态", readConverterExp = "0=正常,1=停用")
    private String status;
}
zhitan-airconditioner/src/main/java/com/zhitan/airconditioner/domain/AirConditionerLog.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,148 @@
package com.zhitan.airconditioner.domain;
import com.zhitan.common.annotation.Excel;
import com.zhitan.common.core.domain.BaseEntity;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import java.util.Date;
/**
 * ç©ºè°ƒæ“ä½œæ—¥å¿—对象 sys_air_conditioner_log
 *
 * @author zhitan
 */
public class AirConditionerLog extends BaseEntity
{
    private static final long serialVersionUID = 1L;
    /** æ—¥å¿—ID */
    private Long id;
    /** ç©ºè°ƒID */
    @Excel(name = "空调ID")
    private Long airConditionerId;
    /** ç©ºè°ƒåç§° */
    @Excel(name = "空调名称")
    private String airConditionerName;
    /** æ“ä½œç±»åž‹ï¼ˆ0手动 1自动) */
    @Excel(name = "操作类型", readConverterExp = "0=手动,1=自动")
    private String operateType;
    /** æ“ä½œæ¨¡å¼ï¼ˆ0制冷开机 1制热开机 2关机) */
    @Excel(name = "操作模式", readConverterExp = "0=制冷开机,1=制热开机,2=关机")
    private String operateMode;
    /** æ“ä½œæ—¶é—´ */
    @Excel(name = "操作时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
    private Date operateTime;
    /** æ“ä½œç»“果(0成功 1失败) */
    @Excel(name = "操作结果", readConverterExp = "0=成功,1=失败")
    private String operateResult;
    /** æ“ä½œæ¶ˆæ¯ */
    @Excel(name = "操作消息")
    private String operateMsg;
    public void setId(Long id)
    {
        this.id = id;
    }
    public Long getId()
    {
        return id;
    }
    public void setAirConditionerId(Long airConditionerId)
    {
        this.airConditionerId = airConditionerId;
    }
    public Long getAirConditionerId()
    {
        return airConditionerId;
    }
    public void setAirConditionerName(String airConditionerName)
    {
        this.airConditionerName = airConditionerName;
    }
    public String getAirConditionerName()
    {
        return airConditionerName;
    }
    public void setOperateType(String operateType)
    {
        this.operateType = operateType;
    }
    public String getOperateType()
    {
        return operateType;
    }
    public void setOperateMode(String operateMode)
    {
        this.operateMode = operateMode;
    }
    public String getOperateMode()
    {
        return operateMode;
    }
    public void setOperateTime(Date operateTime)
    {
        this.operateTime = operateTime;
    }
    public Date getOperateTime()
    {
        return operateTime;
    }
    public void setOperateResult(String operateResult)
    {
        this.operateResult = operateResult;
    }
    public String getOperateResult()
    {
        return operateResult;
    }
    public void setOperateMsg(String operateMsg)
    {
        this.operateMsg = operateMsg;
    }
    public String getOperateMsg()
    {
        return operateMsg;
    }
    @Override
    public String toString() {
        return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
            .append("id", getId())
            .append("airConditionerId", getAirConditionerId())
            .append("airConditionerName", getAirConditionerName())
            .append("operateType", getOperateType())
            .append("operateMode", getOperateMode())
            .append("operateTime", getOperateTime())
            .append("operateResult", getOperateResult())
            .append("operateMsg", getOperateMsg())
            .append("createBy", getCreateBy())
            .append("createTime", getCreateTime())
            .append("updateBy", getUpdateBy())
            .append("updateTime", getUpdateTime())
            .append("remark", getRemark())
            .toString();
    }
}
zhitan-airconditioner/src/main/java/com/zhitan/airconditioner/domain/AirConditionerSchedule.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,48 @@
package com.zhitan.airconditioner.domain;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.zhitan.common.annotation.Excel;
import com.zhitan.common.core.domain.BaseEntity;
import lombok.Data;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import java.util.Date;
/**
 * ç©ºè°ƒå®šæ—¶ä»»åŠ¡å¯¹è±¡ sys_air_conditioner_schedule
 *
 * @author zhitan
 */
@Data
public class AirConditionerSchedule extends BaseEntity
{
    private static final long serialVersionUID = 1L;
    /** å®šæ—¶ä»»åŠ¡ID */
    private Long id;
    /** ç©ºè°ƒID */
    @Excel(name = "空调ID")
    private Long airConditionerId;
    /** å¼€å§‹æ—¶é—´ */
    @Excel(name = "开始时间", width = 30, dateFormat = "HH:mm:ss")
    @JsonFormat(pattern = "HH:mm:ss")
    private Date startTime;
    /** ç»“束时间 */
    @Excel(name = "结束时间", width = 30, dateFormat = "HH:mm:ss")
    @JsonFormat(pattern = "HH:mm:ss")
    private Date offTime;
    /** æŽ§åˆ¶æ¨¡å¼ï¼ˆ0制冷 1制热) */
    @Excel(name = "控制模式", readConverterExp = "0=制冷,1=制热")
    private String controlMode;
    /** çŠ¶æ€ï¼ˆ0正常 1停用) */
    @Excel(name = "状态", readConverterExp = "0=正常,1=停用")
    private String status;
}
zhitan-airconditioner/src/main/java/com/zhitan/airconditioner/mapper/AirConditionerLogMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,72 @@
package com.zhitan.airconditioner.mapper;
import java.util.List;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.zhitan.airconditioner.domain.AirConditionerLog;
import org.apache.ibatis.annotations.Mapper;
/**
 * ç©ºè°ƒæ“ä½œæ—¥å¿—Mapper接口
 *
 * @author zhitan
 */
@Mapper
public interface AirConditionerLogMapper extends BaseMapper<AirConditionerLog>
{
    /**
     * æŸ¥è¯¢ç©ºè°ƒæ“ä½œæ—¥å¿—列表
     *
     * @param airConditionerLog ç©ºè°ƒæ“ä½œæ—¥å¿—信息
     * @return ç©ºè°ƒæ“ä½œæ—¥å¿—集合
     */
    public List<AirConditionerLog> selectAirConditionerLogList(AirConditionerLog airConditionerLog);
    /**
     * æŸ¥è¯¢ç©ºè°ƒæ“ä½œæ—¥å¿—信息
     *
     * @param id ç©ºè°ƒæ“ä½œæ—¥å¿—ID
     * @return ç©ºè°ƒæ“ä½œæ—¥å¿—信息
     */
    public AirConditionerLog selectAirConditionerLogById(Long id);
    /**
     * æ ¹æ®ç©ºè°ƒID查询操作日志列表
     *
     * @param airConditionerId ç©ºè°ƒID
     * @return æ“ä½œæ—¥å¿—列表
     */
    public List<AirConditionerLog> selectAirConditionerLogByAirConditionerId(Long airConditionerId);
    /**
     * æ–°å¢žç©ºè°ƒæ“ä½œæ—¥å¿—
     *
     * @param airConditionerLog ç©ºè°ƒæ“ä½œæ—¥å¿—信息
     * @return ç»“æžœ
     */
    public int insertAirConditionerLog(AirConditionerLog airConditionerLog);
    /**
     * åˆ é™¤ç©ºè°ƒæ“ä½œæ—¥å¿—
     *
     * @param id ç©ºè°ƒæ“ä½œæ—¥å¿—ID
     * @return ç»“æžœ
     */
    public int deleteAirConditionerLogById(Long id);
    /**
     * æ‰¹é‡åˆ é™¤ç©ºè°ƒæ“ä½œæ—¥å¿—
     *
     * @param ids éœ€è¦åˆ é™¤çš„æ•°æ®ID
     * @return ç»“æžœ
     */
    public int deleteAirConditionerLogByIds(Long[] ids);
    /**
     * æ ¹æ®ç©ºè°ƒID删除操作日志
     *
     * @param airConditionerId ç©ºè°ƒID
     * @return ç»“æžœ
     */
    public int deleteAirConditionerLogByAirConditionerId(Long airConditionerId);
}
zhitan-airconditioner/src/main/java/com/zhitan/airconditioner/mapper/AirConditionerMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,72 @@
package com.zhitan.airconditioner.mapper;
import java.util.List;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.zhitan.airconditioner.domain.AirConditioner;
import org.apache.ibatis.annotations.Mapper;
/**
 * ç©ºè°ƒæŽ§åˆ¶å™¨Mapper接口
 *
 * @author zhitan
 */
@Mapper
public interface AirConditionerMapper extends BaseMapper<AirConditioner>
{
    /**
     * æŸ¥è¯¢ç©ºè°ƒæŽ§åˆ¶å™¨åˆ—表
     *
     * @param airConditioner ç©ºè°ƒæŽ§åˆ¶å™¨ä¿¡æ¯
     * @return ç©ºè°ƒæŽ§åˆ¶å™¨é›†åˆ
     */
    public List<AirConditioner> selectAirConditionerList(AirConditioner airConditioner);
    /**
     * æŸ¥è¯¢ç©ºè°ƒæŽ§åˆ¶å™¨ä¿¡æ¯
     *
     * @param id ç©ºè°ƒæŽ§åˆ¶å™¨ID
     * @return ç©ºè°ƒæŽ§åˆ¶å™¨ä¿¡æ¯
     */
    public AirConditioner selectAirConditionerById(Long id);
    /**
     * æ ¹æ®æŽ§åˆ¶å™¨ID查询空调控制器信息
     *
     * @param controllerId æŽ§åˆ¶å™¨ID
     * @return ç©ºè°ƒæŽ§åˆ¶å™¨ä¿¡æ¯
     */
    public AirConditioner selectAirConditionerByControllerId(String controllerId);
    /**
     * æ–°å¢žç©ºè°ƒæŽ§åˆ¶å™¨
     *
     * @param airConditioner ç©ºè°ƒæŽ§åˆ¶å™¨ä¿¡æ¯
     * @return ç»“æžœ
     */
    public int insertAirConditioner(AirConditioner airConditioner);
    /**
     * ä¿®æ”¹ç©ºè°ƒæŽ§åˆ¶å™¨
     *
     * @param airConditioner ç©ºè°ƒæŽ§åˆ¶å™¨ä¿¡æ¯
     * @return ç»“æžœ
     */
    public int updateAirConditioner(AirConditioner airConditioner);
    /**
     * åˆ é™¤ç©ºè°ƒæŽ§åˆ¶å™¨
     *
     * @param id ç©ºè°ƒæŽ§åˆ¶å™¨ID
     * @return ç»“æžœ
     */
    public int deleteAirConditionerById(Long id);
    /**
     * æ‰¹é‡åˆ é™¤ç©ºè°ƒæŽ§åˆ¶å™¨
     *
     * @param ids éœ€è¦åˆ é™¤çš„æ•°æ®ID
     * @return ç»“æžœ
     */
    public int deleteAirConditionerByIds(Long[] ids);
}
zhitan-airconditioner/src/main/java/com/zhitan/airconditioner/mapper/AirConditionerScheduleMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,99 @@
package com.zhitan.airconditioner.mapper;
import java.util.List;
import java.util.Date;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.zhitan.airconditioner.domain.AirConditionerSchedule;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
/**
 * ç©ºè°ƒå®šæ—¶ä»»åŠ¡Mapper接口
 *
 * @author zhitan
 */
@Mapper
public interface AirConditionerScheduleMapper extends BaseMapper<AirConditionerSchedule>
{
    /**
     * æŸ¥è¯¢ç©ºè°ƒå®šæ—¶ä»»åŠ¡åˆ—è¡¨
     *
     * @param airConditionerSchedule ç©ºè°ƒå®šæ—¶ä»»åŠ¡ä¿¡æ¯
     * @return ç©ºè°ƒå®šæ—¶ä»»åŠ¡é›†åˆ
     */
    public List<AirConditionerSchedule> selectAirConditionerScheduleList(AirConditionerSchedule airConditionerSchedule);
    /**
     * æŸ¥è¯¢ç©ºè°ƒå®šæ—¶ä»»åŠ¡ä¿¡æ¯
     *
     * @param id ç©ºè°ƒå®šæ—¶ä»»åŠ¡ID
     * @return ç©ºè°ƒå®šæ—¶ä»»åŠ¡ä¿¡æ¯
     */
    public AirConditionerSchedule selectAirConditionerScheduleById(Long id);
    /**
     * æ ¹æ®ç©ºè°ƒID查询定时任务列表
     *
     * @param airConditionerId ç©ºè°ƒID
     * @return å®šæ—¶ä»»åŠ¡åˆ—è¡¨
     */
    public List<AirConditionerSchedule> selectAirConditionerScheduleByAirConditionerId(Long airConditionerId);
    /**
     * æŸ¥è¯¢å½“前时间内需要执行的定时任务
     *
     * @param currentTime å½“前时间
     * @return å®šæ—¶ä»»åŠ¡åˆ—è¡¨
     */
    public List<AirConditionerSchedule> selectActiveSchedules(@Param("currentTime") Date currentTime);
    /**
     * æ ¹æ®æ—¶é—´ç‚¹æŸ¥è¯¢éœ€è¦æ‰§è¡Œçš„定时任务
     * æŸ¥è¯¢å¼€å§‹æ—¶é—´æˆ–结束时间与指定时间点匹配的任务
     *
     * @param timePoint æ—¶é—´ç‚¹ï¼Œæ ¼å¼ä¸ºHH:mm
     * @return å®šæ—¶ä»»åŠ¡åˆ—è¡¨
     */
    public List<AirConditionerSchedule> selectSchedulesByTimePoint(@Param("timePoint") String timePoint);
    /**
     * æ–°å¢žç©ºè°ƒå®šæ—¶ä»»åŠ¡
     *
     * @param airConditionerSchedule ç©ºè°ƒå®šæ—¶ä»»åŠ¡ä¿¡æ¯
     * @return ç»“æžœ
     */
    public int insertAirConditionerSchedule(AirConditionerSchedule airConditionerSchedule);
    /**
     * ä¿®æ”¹ç©ºè°ƒå®šæ—¶ä»»åŠ¡
     *
     * @param airConditionerSchedule ç©ºè°ƒå®šæ—¶ä»»åŠ¡ä¿¡æ¯
     * @return ç»“æžœ
     */
    public int updateAirConditionerSchedule(AirConditionerSchedule airConditionerSchedule);
    /**
     * åˆ é™¤ç©ºè°ƒå®šæ—¶ä»»åŠ¡
     *
     * @param id ç©ºè°ƒå®šæ—¶ä»»åŠ¡ID
     * @return ç»“æžœ
     */
    public int deleteAirConditionerScheduleById(Long id);
    /**
     * æ‰¹é‡åˆ é™¤ç©ºè°ƒå®šæ—¶ä»»åŠ¡
     *
     * @param ids éœ€è¦åˆ é™¤çš„æ•°æ®ID
     * @return ç»“æžœ
     */
    public int deleteAirConditionerScheduleByIds(Long[] ids);
    /**
     * æ ¹æ®ç©ºè°ƒID删除定时任务
     *
     * @param airConditionerId ç©ºè°ƒID
     * @return ç»“æžœ
     */
    public int deleteAirConditionerScheduleByAirConditionerId(String airConditionerId);
}
zhitan-airconditioner/src/main/java/com/zhitan/airconditioner/service/IAirConditionerLogService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,59 @@
package com.zhitan.airconditioner.service;
import java.util.List;
import com.zhitan.airconditioner.domain.AirConditionerLog;
/**
 * ç©ºè°ƒæ“ä½œæ—¥å¿—Service接口
 *
 * @author zhitan
 */
public interface IAirConditionerLogService {
    /**
     * æŸ¥è¯¢ç©ºè°ƒæ“ä½œæ—¥å¿—列表
     *
     * @param airConditionerLog ç©ºè°ƒæ“ä½œæ—¥å¿—信息
     * @return ç©ºè°ƒæ“ä½œæ—¥å¿—集合
     */
    public List<AirConditionerLog> selectAirConditionerLogList(AirConditionerLog airConditionerLog);
    /**
     * æŸ¥è¯¢ç©ºè°ƒæ“ä½œæ—¥å¿—信息
     *
     * @param id ç©ºè°ƒæ“ä½œæ—¥å¿—ID
     * @return ç©ºè°ƒæ“ä½œæ—¥å¿—信息
     */
    public AirConditionerLog selectAirConditionerLogById(Long id);
    /**
     * æ ¹æ®ç©ºè°ƒID查询操作日志列表
     *
     * @param airConditionerId ç©ºè°ƒID
     * @return æ“ä½œæ—¥å¿—列表
     */
    public List<AirConditionerLog> selectAirConditionerLogByAirConditionerId(Long airConditionerId);
    /**
     * æ–°å¢žç©ºè°ƒæ“ä½œæ—¥å¿—
     *
     * @param airConditionerLog ç©ºè°ƒæ“ä½œæ—¥å¿—信息
     * @return ç»“æžœ
     */
    public int insertAirConditionerLog(AirConditionerLog airConditionerLog);
    /**
     * åˆ é™¤ç©ºè°ƒæ“ä½œæ—¥å¿—信息
     *
     * @param id ç©ºè°ƒæ“ä½œæ—¥å¿—ID
     * @return ç»“æžœ
     */
    public int deleteAirConditionerLogById(Long id);
    /**
     * æ‰¹é‡åˆ é™¤ç©ºè°ƒæ“ä½œæ—¥å¿—信息
     *
     * @param ids éœ€è¦åˆ é™¤çš„空调操作日志ID
     * @return ç»“æžœ
     */
    public int deleteAirConditionerLogByIds(Long[] ids);
}
zhitan-airconditioner/src/main/java/com/zhitan/airconditioner/service/IAirConditionerScheduleService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,83 @@
package com.zhitan.airconditioner.service;
import java.util.List;
import com.zhitan.airconditioner.domain.AirConditionerSchedule;
/**
 * ç©ºè°ƒå®šæ—¶ä»»åŠ¡Service接口
 *
 * @author zhitan
 */
public interface IAirConditionerScheduleService
{
    /**
     * æŸ¥è¯¢ç©ºè°ƒå®šæ—¶ä»»åŠ¡åˆ—è¡¨
     *
     * @param airConditionerSchedule ç©ºè°ƒå®šæ—¶ä»»åŠ¡ä¿¡æ¯
     * @return ç©ºè°ƒå®šæ—¶ä»»åŠ¡é›†åˆ
     */
    public List<AirConditionerSchedule> selectAirConditionerScheduleList(AirConditionerSchedule airConditionerSchedule);
    /**
     * æŸ¥è¯¢ç©ºè°ƒå®šæ—¶ä»»åŠ¡ä¿¡æ¯
     *
     * @param id ç©ºè°ƒå®šæ—¶ä»»åŠ¡ID
     * @return ç©ºè°ƒå®šæ—¶ä»»åŠ¡ä¿¡æ¯
     */
    public AirConditionerSchedule selectAirConditionerScheduleById(Long id);
    /**
     * æ ¹æ®ç©ºè°ƒID查询定时任务列表
     *
     * @param airConditionerId ç©ºè°ƒID
     * @return å®šæ—¶ä»»åŠ¡åˆ—è¡¨
     */
    public List<AirConditionerSchedule> selectAirConditionerScheduleByAirConditionerId(Long airConditionerId);
    /**
     * æ–°å¢žç©ºè°ƒå®šæ—¶ä»»åŠ¡
     *
     * @param airConditionerSchedule ç©ºè°ƒå®šæ—¶ä»»åŠ¡ä¿¡æ¯
     * @return ç»“æžœ
     */
    public int insertAirConditionerSchedule(AirConditionerSchedule airConditionerSchedule);
    /**
     * ä¿®æ”¹ç©ºè°ƒå®šæ—¶ä»»åŠ¡
     *
     * @param airConditionerSchedule ç©ºè°ƒå®šæ—¶ä»»åŠ¡ä¿¡æ¯
     * @return ç»“æžœ
     */
    public int updateAirConditionerSchedule(AirConditionerSchedule airConditionerSchedule);
    /**
     * æ‰¹é‡åˆ é™¤ç©ºè°ƒå®šæ—¶ä»»åŠ¡
     *
     * @param ids éœ€è¦åˆ é™¤çš„空调定时任务ID
     * @return ç»“æžœ
     */
    public int deleteAirConditionerScheduleByIds(Long[] ids);
    /**
     * åˆ é™¤ç©ºè°ƒå®šæ—¶ä»»åŠ¡ä¿¡æ¯
     *
     * @param id ç©ºè°ƒå®šæ—¶ä»»åŠ¡ID
     * @return ç»“æžœ
     */
    public int deleteAirConditionerScheduleById(Long id);
    /**
     * æ ¹æ®ç©ºè°ƒID删除定时任务
     *
     * @param airConditionerId ç©ºè°ƒID
     * @return ç»“æžœ
     */
    public int deleteAirConditionerScheduleByAirConditionerId(String airConditionerId);
    /**
     * æ‰§è¡Œç©ºè°ƒå®šæ—¶ä»»åŠ¡
     *
     * @return ç»“æžœ
     */
    public void executeScheduleTasks();
}
zhitan-airconditioner/src/main/java/com/zhitan/airconditioner/service/IAirConditionerService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,78 @@
package com.zhitan.airconditioner.service;
import java.util.List;
import com.zhitan.airconditioner.domain.AirConditioner;
/**
 * ç©ºè°ƒæŽ§åˆ¶å™¨Service接口
 *
 * @author zhitan
 */
public interface IAirConditionerService
{
    /**
     * æŸ¥è¯¢ç©ºè°ƒæŽ§åˆ¶å™¨åˆ—表
     *
     * @param airConditioner ç©ºè°ƒæŽ§åˆ¶å™¨ä¿¡æ¯
     * @return ç©ºè°ƒæŽ§åˆ¶å™¨é›†åˆ
     */
    public List<AirConditioner> selectAirConditionerList(AirConditioner airConditioner);
    /**
     * æŸ¥è¯¢ç©ºè°ƒæŽ§åˆ¶å™¨ä¿¡æ¯
     *
     * @param id ç©ºè°ƒæŽ§åˆ¶å™¨ID
     * @return ç©ºè°ƒæŽ§åˆ¶å™¨ä¿¡æ¯
     */
    public AirConditioner selectAirConditionerById(Long id);
    /**
     * æ ¹æ®æŽ§åˆ¶å™¨ID查询空调控制器信息
     *
     * @param controllerId æŽ§åˆ¶å™¨ID
     * @return ç©ºè°ƒæŽ§åˆ¶å™¨ä¿¡æ¯
     */
    public AirConditioner selectAirConditionerByControllerId(String controllerId);
    /**
     * æ–°å¢žç©ºè°ƒæŽ§åˆ¶å™¨
     *
     * @param airConditioner ç©ºè°ƒæŽ§åˆ¶å™¨ä¿¡æ¯
     * @return ç»“æžœ
     */
    public int insertAirConditioner(AirConditioner airConditioner);
    /**
     * ä¿®æ”¹ç©ºè°ƒæŽ§åˆ¶å™¨
     *
     * @param airConditioner ç©ºè°ƒæŽ§åˆ¶å™¨ä¿¡æ¯
     * @return ç»“æžœ
     */
    public int updateAirConditioner(AirConditioner airConditioner);
    /**
     * æ‰¹é‡åˆ é™¤ç©ºè°ƒæŽ§åˆ¶å™¨
     *
     * @param ids éœ€è¦åˆ é™¤çš„空调控制器ID
     * @return ç»“æžœ
     */
    public int deleteAirConditionerByIds(Long[] ids);
    /**
     * åˆ é™¤ç©ºè°ƒæŽ§åˆ¶å™¨ä¿¡æ¯
     *
     * @param id ç©ºè°ƒæŽ§åˆ¶å™¨ID
     * @return ç»“æžœ
     */
    public int deleteAirConditionerById(Long id);
    /**
     * æŽ§åˆ¶ç©ºè°ƒå¼€å…³æœº
     *
     * @param id ç©ºè°ƒID
     * @param mode æŽ§åˆ¶æ¨¡å¼ï¼ˆ0制冷 1制热 2关机)
     * @param operateType æ“ä½œç±»åž‹ï¼ˆ0手动 1自动)
     * @return ç»“æžœ
     */
    public boolean controlAirConditioner(Long id, String mode, String operateType);
}
zhitan-airconditioner/src/main/java/com/zhitan/airconditioner/service/impl/AirConditionerLogServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,85 @@
package com.zhitan.airconditioner.service.impl;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.zhitan.airconditioner.mapper.AirConditionerLogMapper;
import com.zhitan.airconditioner.domain.AirConditionerLog;
import com.zhitan.airconditioner.service.IAirConditionerLogService;
/**
 * ç©ºè°ƒæ“ä½œæ—¥å¿—Service业务层处理
 *
 * @author zhitan
 */
@Service
public class AirConditionerLogServiceImpl implements IAirConditionerLogService {
    @Autowired
    private AirConditionerLogMapper airConditionerLogMapper;
    /**
     * æŸ¥è¯¢ç©ºè°ƒæ“ä½œæ—¥å¿—列表
     *
     * @param airConditionerLog ç©ºè°ƒæ“ä½œæ—¥å¿—信息
     * @return ç©ºè°ƒæ“ä½œæ—¥å¿—集合
     */
    @Override
    public List<AirConditionerLog> selectAirConditionerLogList(AirConditionerLog airConditionerLog) {
        return airConditionerLogMapper.selectAirConditionerLogList(airConditionerLog);
    }
    /**
     * æŸ¥è¯¢ç©ºè°ƒæ“ä½œæ—¥å¿—信息
     *
     * @param id ç©ºè°ƒæ“ä½œæ—¥å¿—ID
     * @return ç©ºè°ƒæ“ä½œæ—¥å¿—信息
     */
    @Override
    public AirConditionerLog selectAirConditionerLogById(Long id) {
        return airConditionerLogMapper.selectAirConditionerLogById(id);
    }
    /**
     * æ ¹æ®ç©ºè°ƒID查询操作日志列表
     *
     * @param airConditionerId ç©ºè°ƒID
     * @return æ“ä½œæ—¥å¿—列表
     */
    @Override
    public List<AirConditionerLog> selectAirConditionerLogByAirConditionerId(Long airConditionerId) {
        return airConditionerLogMapper.selectAirConditionerLogByAirConditionerId(airConditionerId);
    }
    /**
     * æ–°å¢žç©ºè°ƒæ“ä½œæ—¥å¿—
     *
     * @param airConditionerLog ç©ºè°ƒæ“ä½œæ—¥å¿—信息
     * @return ç»“æžœ
     */
    @Override
    public int insertAirConditionerLog(AirConditionerLog airConditionerLog) {
        return airConditionerLogMapper.insertAirConditionerLog(airConditionerLog);
    }
    /**
     * åˆ é™¤ç©ºè°ƒæ“ä½œæ—¥å¿—信息
     *
     * @param id ç©ºè°ƒæ“ä½œæ—¥å¿—ID
     * @return ç»“æžœ
     */
    @Override
    public int deleteAirConditionerLogById(Long id) {
        return airConditionerLogMapper.deleteAirConditionerLogById(id);
    }
    /**
     * æ‰¹é‡åˆ é™¤ç©ºè°ƒæ“ä½œæ—¥å¿—信息
     *
     * @param ids éœ€è¦åˆ é™¤çš„空调操作日志ID
     * @return ç»“æžœ
     */
    @Override
    public int deleteAirConditionerLogByIds(Long[] ids) {
        return airConditionerLogMapper.deleteAirConditionerLogByIds(ids);
    }
}
zhitan-airconditioner/src/main/java/com/zhitan/airconditioner/service/impl/AirConditionerScheduleServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,506 @@
package com.zhitan.airconditioner.service.impl;
import java.text.SimpleDateFormat;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;
import com.zhitan.airconditioner.domain.AirConditionerSchedule;
import com.zhitan.airconditioner.mapper.AirConditionerScheduleMapper;
import com.zhitan.airconditioner.service.IAirConditionerScheduleService;
import com.zhitan.airconditioner.service.IAirConditionerService;
import com.zhitan.common.utils.DateTimeUtil;
import com.zhitan.common.utils.DateUtils;
import com.zhitan.common.utils.SecurityUtils;
import com.zhitan.system.service.ISysHolidayService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
/**
 * ç©ºè°ƒå®šæ—¶ä»»åŠ¡Service业务层处理
 *
 * @author zhitan
 */
/**
 * ç©ºè°ƒå®šæ—¶ä»»åŠ¡Service业务层处理
 *
 * @author zhitan
 */
@Service
public class AirConditionerScheduleServiceImpl implements IAirConditionerScheduleService
{
    private static final Logger log = LoggerFactory.getLogger(AirConditionerScheduleServiceImpl.class);
    @Autowired
    private AirConditionerScheduleMapper airConditionerScheduleMapper;
    @Autowired
    private IAirConditionerService airConditionerService;
    @Autowired
    private TaskScheduler taskScheduler;
    @Autowired
    private ISysHolidayService sysHolidayService;
    // å­˜å‚¨æ‰€æœ‰è°ƒåº¦çš„任务,key为任务ID,value为调度的Future对象
    private final Map<String, ScheduledFuture<?>> scheduledTasks = new ConcurrentHashMap<>();
    /**
     * åˆå§‹åŒ–定时任务
     * ä»Žæ•°æ®åº“加载所有定时任务并进行调度
     */
    @PostConstruct
    public void init() {
        log.info("空调定时任务调度器初始化开始");
        // æŸ¥è¯¢æ‰€æœ‰æœ‰æ•ˆçš„定时任务
        AirConditionerSchedule query = new AirConditionerSchedule();
        query.setStatus("0"); // çŠ¶æ€æ­£å¸¸çš„ä»»åŠ¡
        List<AirConditionerSchedule> schedules = airConditionerScheduleMapper.selectAirConditionerScheduleList(query);
        if (schedules.isEmpty()) {
            log.info("没有找到需要调度的空调定时任务");
            return;
        }
        log.info("找到{}个需要调度的空调定时任务", schedules.size());
        // ä¸ºæ¯ä¸ªä»»åŠ¡åˆ›å»ºè°ƒåº¦
        for (AirConditionerSchedule schedule : schedules) {
            scheduleTask(schedule);
        }
        log.info("空调定时任务调度器已启动,使用基于数据库的精确调度机制");
    }
    /**
     * åº”用关闭时取消所有定时任务
     */
    @PreDestroy
    public void destroy() {
        for (Map.Entry<String, ScheduledFuture<?>> entry : scheduledTasks.entrySet()) {
            entry.getValue().cancel(false);
        }
        scheduledTasks.clear();
        log.info("空调定时任务调度器已关闭,所有任务已取消");
    }
    /**
     * æŸ¥è¯¢ç©ºè°ƒå®šæ—¶ä»»åŠ¡åˆ—è¡¨
     *
     * @param airConditionerSchedule ç©ºè°ƒå®šæ—¶ä»»åŠ¡ä¿¡æ¯
     * @return ç©ºè°ƒå®šæ—¶ä»»åŠ¡é›†åˆ
     */
    @Override
    public List<AirConditionerSchedule> selectAirConditionerScheduleList(AirConditionerSchedule airConditionerSchedule)
    {
        return airConditionerScheduleMapper.selectAirConditionerScheduleList(airConditionerSchedule);
    }
    /**
     * æŸ¥è¯¢ç©ºè°ƒå®šæ—¶ä»»åŠ¡ä¿¡æ¯
     *
     * @param id ç©ºè°ƒå®šæ—¶ä»»åŠ¡ID
     * @return ç©ºè°ƒå®šæ—¶ä»»åŠ¡ä¿¡æ¯
     */
    @Override
    public AirConditionerSchedule selectAirConditionerScheduleById(Long id)
    {
        return airConditionerScheduleMapper.selectAirConditionerScheduleById(id);
    }
    /**
     * æ ¹æ®ç©ºè°ƒID查询定时任务列表
     *
     * @param airConditionerId ç©ºè°ƒID
     * @return å®šæ—¶ä»»åŠ¡åˆ—è¡¨
     */
    @Override
    public List<AirConditionerSchedule> selectAirConditionerScheduleByAirConditionerId(Long airConditionerId)
    {
        return airConditionerScheduleMapper.selectAirConditionerScheduleByAirConditionerId(airConditionerId);
    }
    /**
     * æ–°å¢žç©ºè°ƒå®šæ—¶ä»»åŠ¡
     *
     * @param airConditionerSchedule ç©ºè°ƒå®šæ—¶ä»»åŠ¡ä¿¡æ¯
     * @return ç»“æžœ
     */
    @Override
    public int insertAirConditionerSchedule(AirConditionerSchedule airConditionerSchedule)
    {
        airConditionerSchedule.setCreateTime(DateUtils.getNowDate());
        airConditionerSchedule.setCreateBy(SecurityUtils.getUsername());
        int rows = airConditionerScheduleMapper.insertAirConditionerSchedule(airConditionerSchedule);
        if (rows > 0) {
            // é‡æ–°è°ƒåº¦ä»»åŠ¡ï¼Œä½¿æ–°æ·»åŠ çš„å®šæ—¶ä»»åŠ¡ç”Ÿæ•ˆ
            rescheduleTask();
        }
        return rows;
    }
    /**
     * ä¿®æ”¹ç©ºè°ƒå®šæ—¶ä»»åŠ¡
     *
     * @param airConditionerSchedule ç©ºè°ƒå®šæ—¶ä»»åŠ¡ä¿¡æ¯
     * @return ç»“æžœ
     */
    @Override
    public int updateAirConditionerSchedule(AirConditionerSchedule airConditionerSchedule)
    {
        airConditionerSchedule.setUpdateTime(DateUtils.getNowDate());
        airConditionerSchedule.setUpdateBy(SecurityUtils.getUsername());
        int rows = airConditionerScheduleMapper.updateAirConditionerSchedule(airConditionerSchedule);
        if (rows > 0) {
            // é‡æ–°è°ƒåº¦ä»»åŠ¡ï¼Œä½¿ä¿®æ”¹åŽçš„å®šæ—¶ä»»åŠ¡ç”Ÿæ•ˆ
            rescheduleTask();
        }
        return rows;
    }
    /**
     * æ‰¹é‡åˆ é™¤ç©ºè°ƒå®šæ—¶ä»»åŠ¡
     *
     * @param ids éœ€è¦åˆ é™¤çš„空调定时任务ID
     * @return ç»“æžœ
     */
    @Override
    public int deleteAirConditionerScheduleByIds(Long[] ids)
    {
        int rows = airConditionerScheduleMapper.deleteAirConditionerScheduleByIds(ids);
        if (rows > 0) {
            // é‡æ–°è°ƒåº¦ä»»åŠ¡ï¼Œä½¿åˆ é™¤æ“ä½œç”Ÿæ•ˆ
            rescheduleTask();
        }
        return rows;
    }
    /**
     * åˆ é™¤ç©ºè°ƒå®šæ—¶ä»»åŠ¡ä¿¡æ¯
     *
     * @param id ç©ºè°ƒå®šæ—¶ä»»åŠ¡ID
     * @return ç»“æžœ
     */
    @Override
    public int deleteAirConditionerScheduleById(Long id)
    {
        int rows = airConditionerScheduleMapper.deleteAirConditionerScheduleById(id);
        if (rows > 0) {
            // é‡æ–°è°ƒåº¦ä»»åŠ¡ï¼Œä½¿åˆ é™¤æ“ä½œç”Ÿæ•ˆ
            rescheduleTask();
        }
        return rows;
    }
    /**
     * æ ¹æ®ç©ºè°ƒID删除定时任务
     *
     * @param airConditionerId ç©ºè°ƒID
     * @return ç»“æžœ
     */
    @Override
    public int deleteAirConditionerScheduleByAirConditionerId(String airConditionerId)
    {
        int rows = airConditionerScheduleMapper.deleteAirConditionerScheduleByAirConditionerId(airConditionerId);
        if (rows > 0) {
            // é‡æ–°è°ƒåº¦ä»»åŠ¡ï¼Œä½¿åˆ é™¤æ“ä½œç”Ÿæ•ˆ
            rescheduleTask();
        }
        return rows;
    }
    /**
     * æ‰§è¡Œç©ºè°ƒå®šæ—¶ä»»åŠ¡
     * æ­¤æ–¹æ³•保留用于兼容接口,在新的实现中不再使用
     * æ–°çš„实现为每个具体的时间点创建独立的调度任务,不再需要每分钟检查一次
     */
    @Override
    public void executeScheduleTasks()
    {
        log.info("executeScheduleTasks方法已被调用,但在新的实现中不再使用");
        log.info("当前使用基于数据库的精确定时任务调度机制,为每个时间点创建独立的调度任务");
        // å¦‚果有必要,可以在这里重新调度所有任务
        // rescheduleTask();
    }
    /**
     * é‡æ–°è°ƒåº¦ä»»åŠ¡
     * å½“定时任务发生变化时(如添加、修改、删除定时任务)调用此方法
     * å–消所有当前的调度任务并重新创建
     */
    public void rescheduleTask() {
        // å–消所有现有的调度任务
        for (Map.Entry<String, ScheduledFuture<?>> entry : scheduledTasks.entrySet()) {
            entry.getValue().cancel(false);
        }
        scheduledTasks.clear();
        // æŸ¥è¯¢æ‰€æœ‰æœ‰æ•ˆçš„定时任务并重新调度
        AirConditionerSchedule query = new AirConditionerSchedule();
        query.setStatus("0"); // çŠ¶æ€æ­£å¸¸çš„ä»»åŠ¡
        List<AirConditionerSchedule> schedules = airConditionerScheduleMapper.selectAirConditionerScheduleList(query);
        if (schedules.isEmpty()) {
            log.info("没有找到需要调度的空调定时任务");
            return;
        }
        log.info("重新调度{}个空调定时任务", schedules.size());
        // ä¸ºæ¯ä¸ªä»»åŠ¡åˆ›å»ºè°ƒåº¦
        for (AirConditionerSchedule schedule : schedules) {
            scheduleTask(schedule);
        }
        log.info("空调定时任务已重新调度完成");
    }
    /**
     * ä¸ºå•个定时任务创建调度
     * åˆ†åˆ«ä¸ºå¼€æœºæ—¶é—´å’Œå…³æœºæ—¶é—´åˆ›å»ºç‹¬ç«‹çš„调度任务
     * å¼€æœºæŒ‡ä»¤åªåœ¨æ³•定工作日添加定时任务,关机指令每天都添加定时任务
     *
     * @param schedule å®šæ—¶ä»»åŠ¡ä¿¡æ¯
     */
    private void scheduleTask(AirConditionerSchedule schedule) {
        if (schedule == null || schedule.getId() == null) {
            log.error("无法调度空或无ID的定时任务");
            return;
        }
        try {
            // ä¸ºå¼€æœºæ—¶é—´åˆ›å»ºè°ƒåº¦
            if (schedule.getStartTime() != null) {
                // å¼€æœºæŒ‡ä»¤åªåœ¨æ³•定工作日添加定时任务
                scheduleSpecificTask(schedule, schedule.getStartTime(), true);
            }
            // ä¸ºå…³æœºæ—¶é—´åˆ›å»ºè°ƒåº¦
            if (schedule.getOffTime() != null) {
                // å…³æœºæŒ‡ä»¤æ¯å¤©éƒ½æ·»åŠ å®šæ—¶ä»»åŠ¡
                scheduleSpecificTask(schedule, schedule.getOffTime(), false);
            }
        } catch (Exception e) {
            log.error("为定时任务创建调度失败,任务ID:{},错误信息:{}", schedule.getId(), e.getMessage());
        }
    }
    /**
     * ä¸ºç‰¹å®šæ—¶é—´ç‚¹åˆ›å»ºè°ƒåº¦ä»»åŠ¡
     *
     * @param schedule å®šæ—¶ä»»åŠ¡ä¿¡æ¯
     * @param targetTime ç›®æ ‡æ—¶é—´ç‚¹
     * @param isStartTime æ˜¯å¦ä¸ºå¼€æœºæ—¶é—´ï¼ˆtrue为开机,false为关机)
     */
    private void scheduleSpecificTask(AirConditionerSchedule schedule, Date targetTime, boolean isStartTime) {
        // è®¡ç®—下一次执行时间
        Date nextExecutionTime = calculateNextExecutionTime(targetTime);
        if (nextExecutionTime == null) {
            log.error("计算下一次执行时间失败,任务ID:{}", schedule.getId());
            return;
        }
        // å¦‚果是开机任务,检查执行日期是否为工作日
        if (isStartTime) {
            // å¦‚果不是工作日,则计算下一个工作日
            while (!isWorkDay(nextExecutionTime)) {
                // ä¸æ˜¯å·¥ä½œæ—¥ï¼Œè·³åˆ°ä¸‹ä¸€å¤©å†æ£€æŸ¥
                nextExecutionTime = DateUtils.addDays(nextExecutionTime, 1);
                // æ›´æ–°æ—¶é—´éƒ¨åˆ†ï¼ˆä¿æŒåŽŸæ¥çš„æ—¶åˆ†ç§’ä¸å˜ï¼‰
                Calendar cal = Calendar.getInstance();
                cal.setTime(nextExecutionTime);
                Calendar targetCal = Calendar.getInstance();
                targetCal.setTime(targetTime);
                cal.set(Calendar.HOUR_OF_DAY, targetCal.get(Calendar.HOUR_OF_DAY));
                cal.set(Calendar.MINUTE, targetCal.get(Calendar.MINUTE));
                cal.set(Calendar.SECOND, targetCal.get(Calendar.SECOND));
                nextExecutionTime = cal.getTime();
            }
            log.info("开机任务已调整为下一个工作日执行,执行时间:{}",
                    new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(nextExecutionTime));
        }
        // åˆ›å»ºä»»åŠ¡ID,格式为:任务ID + "_start"或"_off"
        String taskKey = schedule.getId() + (isStartTime ? "_start" : "_off");
        // å–消已存在的相同任务(如果有)
        ScheduledFuture<?> existingTask = scheduledTasks.get(taskKey);
        if (existingTask != null) {
            existingTask.cancel(false);
            scheduledTasks.remove(taskKey);
        }
        // åˆ›å»ºæ–°çš„调度任务
        ScheduledFuture<?> future = taskScheduler.schedule(
            () -> executeSpecificTask(schedule, isStartTime),
            nextExecutionTime
        );
        // ä¿å­˜è°ƒåº¦ä»»åŠ¡
        scheduledTasks.put(taskKey, future);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        log.info("已为空调ID:{}创建{}定时任务,执行时间:{}",
                schedule.getAirConditionerId(),
                isStartTime ? "开机" : "关机",
                sdf.format(nextExecutionTime));
    }
    /**
     * è®¡ç®—下一次执行时间
     * å¦‚果今天的执行时间已过,则安排在明天的同一时间执行
     *
     * @param targetTime ç›®æ ‡æ—¶é—´
     * @return ä¸‹ä¸€æ¬¡æ‰§è¡Œæ—¶é—´
     */
    private Date calculateNextExecutionTime(Date targetTime) {
        if (targetTime == null) {
            return null;
        }
        // å°†Date转换为LocalTime(只保留时分秒)
        LocalTime targetLocalTime = targetTime.toInstant()
                .atZone(ZoneId.systemDefault())
                .toLocalTime();
        // èŽ·å–å½“å‰æ—¥æœŸå’Œæ—¶é—´
        LocalDateTime now = LocalDateTime.now();
        LocalDate today = now.toLocalDate();
        // åˆ›å»ºä»Šå¤©çš„目标执行时间
        LocalDateTime targetDateTime = LocalDateTime.of(today, targetLocalTime);
        // å¦‚果目标时间已过,安排在明天的同一时间执行
        if (targetDateTime.isBefore(now)) {
            targetDateTime = targetDateTime.plusDays(1);
        }
        // è½¬æ¢å›žDate类型
        return Date.from(targetDateTime.atZone(ZoneId.systemDefault()).toInstant());
    }
    /**
     * æ‰§è¡Œç‰¹å®šçš„定时任务
     *
     * @param schedule å®šæ—¶ä»»åŠ¡ä¿¡æ¯
     * @param isStartTime æ˜¯å¦ä¸ºå¼€æœºæ—¶é—´ï¼ˆtrue为开机,false为关机)
     */
    private void executeSpecificTask(AirConditionerSchedule schedule, boolean isStartTime) {
        try {
            if (isStartTime) {
                // å¼€æœºæ“ä½œ - æ ¹æ®å­£èŠ‚è‡ªåŠ¨åˆ¤æ–­åˆ¶çƒ­æˆ–åˆ¶å†·æ¨¡å¼
                String controlMode = determineControlModeByMonth();
                log.info("执行空调开机任务,空调ID:{},控制模式:{}", schedule.getAirConditionerId(), controlMode);
                // æ‰§è¡Œç©ºè°ƒå¼€æœºæŽ§åˆ¶
                airConditionerService.controlAirConditioner(
                    schedule.getAirConditionerId(),
                    controlMode, // 0制冷或1制热
                    "1" // è‡ªåŠ¨æ“ä½œ
                );
            } else {
                // å…³æœºæ“ä½œ
                log.info("执行空调关机任务,空调ID:{}", schedule.getAirConditionerId());
                // æ‰§è¡Œç©ºè°ƒå…³æœºæŽ§åˆ¶
                airConditionerService.controlAirConditioner(
                    schedule.getAirConditionerId(),
                    "2", // å…³æœº
                    "1" // è‡ªåŠ¨æ“ä½œ
                );
            }
            // ä»»åŠ¡æ‰§è¡Œå®ŒæˆåŽï¼Œé‡æ–°è°ƒåº¦ä¸‹ä¸€å¤©çš„åŒä¸€æ—¶é—´ç‚¹
            // èŽ·å–åŽŸå§‹çš„æ—¶é—´ç‚¹
            Date originalTime = isStartTime ? schedule.getStartTime() : schedule.getOffTime();
            // é‡æ–°è°ƒåº¦
            scheduleSpecificTask(schedule, originalTime, isStartTime);
        } catch (Exception e) {
            log.error("执行空调{}任务失败,空调ID:{},错误信息:{}",
                    isStartTime ? "开机" : "关机",
                    schedule.getAirConditionerId(),
                    e.getMessage());
        }
    }
    /**
     * åˆ¤æ–­æ—¥æœŸæ˜¯å¦ä¸ºå·¥ä½œæ—¥
     * å·¥ä½œæ—¥åˆ¤æ–­é€»è¾‘:
     * 1. å¦‚果是周一至周五,且不是法定节假日,则为工作日
     * 2. å¦‚果是周六日,但是为调休工作日,则为工作日
     *
     * @param date éœ€è¦åˆ¤æ–­çš„æ—¥æœŸ
     * @return å¦‚果是工作日返回true,否则返回false
     */
    private boolean isWorkDay(Date date) {
        // è°ƒç”¨èŠ‚å‡æ—¥æœåŠ¡è¿›è¡Œç²¾ç¡®çš„å·¥ä½œæ—¥åˆ¤æ–­
        return sysHolidayService.isWorkDay(date);
    }
    /**
     * æ ¹æ®æœˆä»½è‡ªåŠ¨åˆ¤æ–­åˆ¶çƒ­æˆ–åˆ¶å†·æ¨¡å¼
     * 5-9月为夏季,使用制冷模式
     * 11-3月为冬季,使用制热模式
     * 4月和10月为过渡季节,根据当前温度判断
     *
     * @return æŽ§åˆ¶æ¨¡å¼ï¼ˆ0制冷 1制热)
     */
    private String determineControlModeByMonth()
    {
        // èŽ·å–å½“å‰æœˆä»½
        int currentMonth = DateTimeUtil.getMonthOfYear(new Date());
        // æ ¹æ®æœˆä»½åˆ¤æ–­å­£èŠ‚
        if (currentMonth >= 5 && currentMonth <= 9)
        {
            // å¤å­£ä½¿ç”¨åˆ¶å†·æ¨¡å¼
            return "0";
        }
        else if ((currentMonth >= 11 && currentMonth <= 12) || (currentMonth >= 1 && currentMonth <= 3))
        {
            // å†¬å­£ä½¿ç”¨åˆ¶çƒ­æ¨¡å¼
            return "1";
        }
        else
        {
            // è¿‡æ¸¡å­£èŠ‚ï¼Œé»˜è®¤ä½¿ç”¨åˆ¶å†·æ¨¡å¼
            // å®žé™…应用中可以根据当前温度判断
            return "0";
        }
    }
    /**
     * æ³¨æ„ï¼šå¯¹äºŽåªå­˜å‚¨æ—¶åˆ†ç§’的时间字段(如开始时间和结束时间),
     * æ•°æ®åº“应该使用TIME类型而非DATETIME或TIMESTAMP类型。
     *
     * MySQL中的TIME类型格式为'HH:MM:SS',只存储时间部分,不包含日期信息。
     * è¿™æ ·å¯ä»¥å‡å°‘存储空间,并且更符合业务需求。
     *
     * åœ¨Java实体类中,虽然使用Date类型表示,但在与数据库交互时,
     * åªæœ‰æ—¶åˆ†ç§’部分会被保存和读取。
     */
}
zhitan-airconditioner/src/main/java/com/zhitan/airconditioner/service/impl/AirConditionerServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,216 @@
package com.zhitan.airconditioner.service.impl;
import java.util.Date;
import java.util.List;
import com.alibaba.fastjson2.JSONObject;
import com.zhitan.airconditioner.domain.AirConditioner;
import com.zhitan.airconditioner.domain.AirConditionerLog;
import com.zhitan.airconditioner.mapper.AirConditionerLogMapper;
import com.zhitan.airconditioner.mapper.AirConditionerMapper;
import com.zhitan.airconditioner.service.IAirConditionerService;
import com.zhitan.common.utils.DateUtils;
import com.zhitan.common.utils.SecurityUtils;
import com.zhitan.framework.mqtt.MqttClientUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
 * ç©ºè°ƒæŽ§åˆ¶å™¨Service业务层处理
 *
 * @author zhitan
 */
@Service
public class AirConditionerServiceImpl implements IAirConditionerService
{
    @Autowired
    private AirConditionerMapper airConditionerMapper;
    @Autowired
    private AirConditionerLogMapper airConditionerLogMapper;
    @Autowired
    private MqttClientUtil mqttClientUtil;
    /**
     * æŸ¥è¯¢ç©ºè°ƒæŽ§åˆ¶å™¨åˆ—表
     *
     * @param airConditioner ç©ºè°ƒæŽ§åˆ¶å™¨ä¿¡æ¯
     * @return ç©ºè°ƒæŽ§åˆ¶å™¨é›†åˆ
     */
    @Override
    public List<AirConditioner> selectAirConditionerList(AirConditioner airConditioner)
    {
        return airConditionerMapper.selectAirConditionerList(airConditioner);
    }
    /**
     * æŸ¥è¯¢ç©ºè°ƒæŽ§åˆ¶å™¨ä¿¡æ¯
     *
     * @param id ç©ºè°ƒæŽ§åˆ¶å™¨ID
     * @return ç©ºè°ƒæŽ§åˆ¶å™¨ä¿¡æ¯
     */
    @Override
    public AirConditioner selectAirConditionerById(Long id)
    {
        return airConditionerMapper.selectAirConditionerById(id);
    }
    /**
     * æ ¹æ®æŽ§åˆ¶å™¨ID查询空调控制器信息
     *
     * @param controllerId æŽ§åˆ¶å™¨ID
     * @return ç©ºè°ƒæŽ§åˆ¶å™¨ä¿¡æ¯
     */
    @Override
    public AirConditioner selectAirConditionerByControllerId(String controllerId)
    {
        return airConditionerMapper.selectAirConditionerByControllerId(controllerId);
    }
    /**
     * æ–°å¢žç©ºè°ƒæŽ§åˆ¶å™¨
     *
     * @param airConditioner ç©ºè°ƒæŽ§åˆ¶å™¨ä¿¡æ¯
     * @return ç»“æžœ
     */
    @Override
    public int insertAirConditioner(AirConditioner airConditioner)
    {
        airConditioner.setCreateTime(DateUtils.getNowDate());
        airConditioner.setCreateBy(SecurityUtils.getUsername());
        return airConditionerMapper.insertAirConditioner(airConditioner);
    }
    /**
     * ä¿®æ”¹ç©ºè°ƒæŽ§åˆ¶å™¨
     *
     * @param airConditioner ç©ºè°ƒæŽ§åˆ¶å™¨ä¿¡æ¯
     * @return ç»“æžœ
     */
    @Override
    public int updateAirConditioner(AirConditioner airConditioner)
    {
        airConditioner.setUpdateTime(DateUtils.getNowDate());
        airConditioner.setUpdateBy(SecurityUtils.getUsername());
        return airConditionerMapper.updateAirConditioner(airConditioner);
    }
    /**
     * æ‰¹é‡åˆ é™¤ç©ºè°ƒæŽ§åˆ¶å™¨
     *
     * @param ids éœ€è¦åˆ é™¤çš„空调控制器ID
     * @return ç»“æžœ
     */
    @Override
    public int deleteAirConditionerByIds(Long[] ids)
    {
        return airConditionerMapper.deleteAirConditionerByIds(ids);
    }
    /**
     * åˆ é™¤ç©ºè°ƒæŽ§åˆ¶å™¨ä¿¡æ¯
     *
     * @param id ç©ºè°ƒæŽ§åˆ¶å™¨ID
     * @return ç»“æžœ
     */
    @Override
    public int deleteAirConditionerById(Long id)
    {
        return airConditionerMapper.deleteAirConditionerById(id);
    }
    /**
     * æŽ§åˆ¶ç©ºè°ƒå¼€å…³æœº
     *
     * @param id ç©ºè°ƒID
     * @param mode æŽ§åˆ¶æ¨¡å¼ï¼ˆ0制冷 1制热 2关机)
     * @param operateType æ“ä½œç±»åž‹ï¼ˆ0手动 1自动)
     * @return ç»“æžœ
     */
    @Override
    public boolean controlAirConditioner(Long id, String mode, String operateType)
    {
        try {
            // æŸ¥è¯¢ç©ºè°ƒä¿¡æ¯
            AirConditioner airConditioner = airConditionerMapper.selectAirConditionerById(id);
            if (airConditioner == null) {
                return false;
            }
            String msg = "{\n" +
                    "  \"rw_prot\": {\n" +
                    "    \"Ver\": \"1.0.1\",\n" +
                    "    \"dir\": \"down\",\n" +
                    "    \"id\": \"00601925021000024737\",\n" +
                    "    \"r_data\": [\n" +
                    "      {\"name\":\"hum\"},\n" +
                    "      {\"name\":\"tmp\"},\n" +
                    "      {\n" +
                    "        \"name\": \"cold\"\n" +
                    "      },\n" +
                    "      {\n" +
                    "        \"name\": \"hot\"\n" +
                    "      },\n" +
                    "      {\n" +
                    "        \"name\": \"off\"\n" +
                    "      }\n" +
                    "    ],\n" +
                    "    \"w_data\": [\n" +
                    "      {\n" +
                    "        \"name\": \"cold\",\n" +
                    "        \"value\": \"0\"\n" +
                    "      },\n" +
                    "      {\n" +
                    "        \"name\": \"hot\",\n" +
                    "        \"value\": \"0\"\n" +
                    "      },\n" +
                    "      {\n" +
                    "        \"name\": \"off\",\n" +
                    "        \"value\": \"0\"\n" +
                    "      }\n" +
                    "    ]\n" +
                    "  }\n" +
                    "}";
            // å°†å­—符串转json
            JSONObject jsonObject = JSONObject.parseObject(msg);
            JSONObject rwProt = jsonObject.getJSONObject("rw_prot");
            rwProt.put("id", airConditioner.getControllerId());
            if ("0".equals(mode)) {
                rwProt.getJSONArray("w_data").getJSONObject(0).put("value", "1");
            } else if ("1".equals(mode)) {
                rwProt.getJSONArray("w_data").getJSONObject(1).put("value", "1");
            } else if ("2".equals(mode)) {
                rwProt.getJSONArray("w_data").getJSONObject(2).put("value", "1");
            }
            // æž„建MQTT消息
            String topic = "lanbao/nygl/sevice/kt1/down";
            String message = jsonObject.toJSONString();
            // å‘送MQTT消息
            mqttClientUtil.sendMessage(topic, message, 2);
            // è®°å½•操作日志
            AirConditionerLog log = new AirConditionerLog();
            log.setAirConditionerId(id);
            log.setAirConditionerName(airConditioner.getName());
            log.setOperateType(operateType);
            log.setOperateMode(mode);
            log.setOperateTime(new Date());
            log.setOperateResult("1"); // æˆåŠŸ
            log.setOperateMsg("操作成功");
            log.setCreateTime(DateUtils.getNowDate());
            if ("1".equals(operateType)) {
                log.setCreateBy("系统");
            } else {
                log.setCreateBy(SecurityUtils.getUsername());
            }
            airConditionerLogMapper.insertAirConditionerLog(log);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
}
zhitan-quartz/pom.xml
@@ -34,6 +34,12 @@
            <groupId>com.zhitan</groupId>
            <artifactId>zhitan-common</artifactId>
        </dependency>
        <dependency>
            <groupId>com.zhitan</groupId>
            <artifactId>zhitan-airconditioner</artifactId>
            <version>2.5.2</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>
zhitan-quartz/src/main/java/com/zhitan/quartz/task/AirConditionerTask.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,25 @@
package com.zhitan.quartz.task;
import com.zhitan.airconditioner.service.IAirConditionerScheduleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
 * ç©ºè°ƒå®šæ—¶ä»»åŠ¡
 *
 * @author zhitan
 */
@Component("airConditionerTask")
public class AirConditionerTask
{
    @Autowired
    private IAirConditionerScheduleService airConditionerScheduleService;
    /**
     * æ‰§è¡Œç©ºè°ƒå®šæ—¶ä»»åŠ¡
     */
    public void executeScheduleTasks()
    {
        airConditionerScheduleService.executeScheduleTasks();
    }
}
zhitan-vue/src/api/airconditioner/airconditioner.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,57 @@
import request from '@/utils/request'
// æŸ¥è¯¢ç©ºè°ƒåˆ—表
export function listAirConditioner(query) {
  return request({
    url: '/system/airconditioner/list',
    method: 'get',
    params: query
  })
}
// æŸ¥è¯¢ç©ºè°ƒè¯¦ç»†
export function getAirConditioner(id) {
  return request({
    url: '/system/airconditioner/' + id,
    method: 'get'
  })
}
// æ–°å¢žç©ºè°ƒ
export function addAirConditioner(data) {
  return request({
    url: '/system/airconditioner',
    method: 'post',
    data: data
  })
}
// ä¿®æ”¹ç©ºè°ƒ
export function updateAirConditioner(data) {
  return request({
    url: '/system/airconditioner',
    method: 'put',
    data: data
  })
}
// åˆ é™¤ç©ºè°ƒ
export function delAirConditioner(id) {
  return request({
    url: '/system/airconditioner/' + id,
    method: 'delete'
  })
}
// æŽ§åˆ¶ç©ºè°ƒå¼€å…³æœº
export function controlAirConditioner(id, mode, operateType) {
  return request({
    url: '/system/airconditioner/control',
    method: 'post',
    data: {
      id: id,
      mode: mode, // 0制冷 1制热 2关机
      operateType: operateType // 0手动 1自动
    }
  })
}
zhitan-vue/src/api/airconditioner/log.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,26 @@
import request from '@/utils/request'
// æŸ¥è¯¢ç©ºè°ƒæ“ä½œæ—¥å¿—列表
export function listLog(query) {
  return request({
    url: '/system/airconditioner/log/list',
    method: 'get',
    params: query
  })
}
// æŸ¥è¯¢ç©ºè°ƒæ“ä½œæ—¥å¿—详细
export function getLog(id) {
  return request({
    url: '/system/airconditioner/log/' + id,
    method: 'get'
  })
}
// æ ¹æ®ç©ºè°ƒID查询操作日志列表
export function getLogByAirConditionerId(airConditionerId) {
  return request({
    url: '/system/airconditioner/log/byAirConditioner/' + airConditionerId,
    method: 'get'
  })
}
zhitan-vue/src/api/airconditioner/schedule.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,52 @@
import request from '@/utils/request'
// æŸ¥è¯¢ç©ºè°ƒå®šæ—¶ä»»åŠ¡åˆ—è¡¨
export function listSchedule(query) {
  return request({
    url: '/system/airconditioner/schedule/list',
    method: 'get',
    params: query
  })
}
// æŸ¥è¯¢ç©ºè°ƒå®šæ—¶ä»»åŠ¡è¯¦ç»†
export function getSchedule(id) {
  return request({
    url: '/system/airconditioner/schedule/' + id,
    method: 'get'
  })
}
// æ ¹æ®ç©ºè°ƒID查询定时任务列表
export function getScheduleByAirConditionerId(airConditionerId) {
  return request({
    url: '/system/airconditioner/schedule/byAirConditioner/' + airConditionerId,
    method: 'get'
  })
}
// æ–°å¢žç©ºè°ƒå®šæ—¶ä»»åŠ¡
export function addSchedule(data) {
  return request({
    url: '/system/airconditioner/schedule',
    method: 'post',
    data: data
  })
}
// ä¿®æ”¹ç©ºè°ƒå®šæ—¶ä»»åŠ¡
export function updateSchedule(data) {
  return request({
    url: '/system/airconditioner/schedule',
    method: 'put',
    data: data
  })
}
// åˆ é™¤ç©ºè°ƒå®šæ—¶ä»»åŠ¡
export function delSchedule(id) {
  return request({
    url: '/system/airconditioner/schedule/' + id,
    method: 'delete'
  })
}
zhitan-vue/src/views/airconditioner/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,76 @@
<template>
  <div class="app-container">
    <el-card class="box-card">
      <template #header>
        <div class="card-header">
          <span>空调控制系统</span>
        </div>
      </template>
      <el-tabs v-model="activeTab">
        <el-tab-pane label="空调列表" name="list">
          <air-conditioner-list ref="airConditionerList" />
        </el-tab-pane>
        <el-tab-pane label="定时任务管理" name="schedule">
          <air-conditioner-schedule ref="airConditionerSchedule" />
        </el-tab-pane>
        <el-tab-pane label="操作日志" name="log">
          <air-conditioner-log ref="airConditionerLog" />
        </el-tab-pane>
      </el-tabs>
    </el-card>
  </div>
</template>
<script setup name="AirConditioner">
import { ref, onMounted, nextTick } from 'vue'
import AirConditionerList from './list.vue'
import AirConditionerSchedule from './schedule.vue'
import AirConditionerLog from './log.vue'
const activeTab = ref('list')
const airConditionerList = ref(null)
const airConditionerSchedule = ref(null)
const scheduleDialogRef = ref(null)
// å®šæ—¶ä»»åŠ¡å¼¹çª—æ˜¾ç¤ºçŠ¶æ€
const scheduleDialogVisible = ref(false)
// å¤„理从空调列表切换到定时任务页面的方法(通过Tab切换)
function handleTabSchedule(airConditionerId) {
  activeTab.value = 'schedule'
  // ç­‰å¾…DOM更新后调用定时任务组件的方法
  nextTick(() => {
    if (airConditionerSchedule.value) {
      airConditionerSchedule.value.loadScheduleByAirConditioner(airConditionerId)
    }
  })
}
// å¤„理显示定时任务弹窗的方法(通过按钮点击)
function handleShowSchedule(airConditionerId) {
  scheduleDialogVisible.value = true
  // ç­‰å¾…DOM更新后调用定时任务组件的方法
  nextTick(() => {
    if (scheduleDialogRef.value) {
      scheduleDialogRef.value.loadScheduleByAirConditioner(airConditionerId)
    }
  })
}
// æš´éœ²æ–¹æ³•给子组件调用
defineExpose({
  activeTab,
  handleShowSchedule,
  handleTabSchedule
})
</script>
<style scoped>
.card-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
}
</style>
zhitan-vue/src/views/airconditioner/list.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,409 @@
<template>
  <div class="app-container">
    <el-row :gutter="20">
      <el-col :span="24">
        <el-form :model="queryParams" ref="queryFormRef" :inline="true" v-show="showSearch" label-width="68px">
          <el-form-item label="空调名称" prop="name">
            <el-input
              v-model="queryParams.name"
              placeholder="请输入空调名称"
              clearable
              @keyup.enter="handleQuery"
            />
          </el-form-item>
          <el-form-item label="安装位置" prop="location">
            <el-input
              v-model="queryParams.location"
              placeholder="请输入安装位置"
              clearable
              @keyup.enter="handleQuery"
            />
          </el-form-item>
          <el-form-item label="状态" prop="status">
            <el-select v-model="queryParams.status" placeholder="空调状态" clearable>
              <el-option
                v-for="dict in statusOptions"
                :key="dict.label"
                :label="dict.label"
                :value="dict.label"
              />
            </el-select>
          </el-form-item>
          <el-form-item>
            <el-button type="primary" :icon="Search" @click="handleQuery">搜索</el-button>
            <el-button :icon="Refresh" @click="resetQuery">重置</el-button>
          </el-form-item>
        </el-form>
        <el-row :gutter="10" class="mb8">
          <el-col :span="1.5">
            <el-button
              type="primary"
              plain
              :icon="Plus"
              @click="handleAdd"
              v-hasPermi="['system:airconditioner:add']"
            >新增</el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button
              type="success"
              plain
              :icon="Edit"
              :disabled="single"
              @click="handleUpdate"
              v-hasPermi="['system:airconditioner:edit']"
            >修改</el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button
              type="danger"
              plain
              :icon="Delete"
              :disabled="multiple"
              @click="handleDelete"
              v-hasPermi="['system:airconditioner:remove']"
            >删除</el-button>
          </el-col>
          <right-toolbar v-model:show-search="showSearch" @queryTable="getList"></right-toolbar>
        </el-row>
        <el-table v-loading="loading" :data="airConditionerList" @selection-change="handleSelectionChange">
          <el-table-column type="selection" width="55" align="center" />
          <el-table-column label="空调ID" align="center" prop="id" />
          <el-table-column label="空调名称" align="center" prop="name" />
          <el-table-column label="控制器ID" align="center" prop="controllerId" />
          <el-table-column label="安装位置" align="center" prop="location" />
          <el-table-column label="状态" align="center" prop="status">
            <template #default="scope">
              <dict-tag :options="statusOptions" :value="scope.row.status"/>
            </template>
          </el-table-column>
          <el-table-column label="控制" align="center"  width="300">
            <template #default="scope">
              <el-button
                type="primary"
                size="small"
                @click="handleControl('0', scope.row)"
              >制冷开机</el-button>
              <el-button
                type="success"
                size="small"
                @click="handleControl('1', scope.row)"
              >制热开机</el-button>
              <el-button
                type="warning"
                size="small"
                @click="handleControl('2', scope.row)"
              >关机</el-button>
            </template>
          </el-table-column>
          <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
            <template #default="scope">
              <el-button
                type="primary"
                link
                :icon="Edit"
                @click="handleUpdate(scope.row)"
                v-hasPermi="['system:airconditioner:edit']"
              >修改</el-button>
              <el-button
                type="primary"
                link
                :icon="Delete"
                @click="handleDelete(scope.row)"
                v-hasPermi="['system:airconditioner:remove']"
              >删除</el-button>
              <el-button
                type="primary"
                link
                :icon="Timer"
                @click="handleShowSchedule(scope.row)"
              >定时</el-button>
            </template>
          </el-table-column>
        </el-table>
        <pagination
          v-show="total>0"
          v-model:page="queryParams.pageNum"
          v-model:limit="queryParams.pageSize"
          :total="total"
          @pagination="getList"
        />
      </el-col>
    </el-row>
    <!-- æ·»åŠ æˆ–ä¿®æ”¹ç©ºè°ƒå¯¹è¯æ¡† -->
    <el-dialog :title="title" v-model="open" width="500px" append-to-body>
      <el-form ref="formRef" :model="form" :rules="rules" label-width="80px">
        <el-form-item label="空调名称" prop="name">
          <el-input v-model="form.name" placeholder="请输入空调名称" />
        </el-form-item>
        <el-form-item label="控制器ID" prop="controllerId">
          <el-input v-model="form.controllerId" placeholder="请输入控制器ID" />
        </el-form-item>
        <el-form-item label="安装位置" prop="location">
          <el-input v-model="form.location" placeholder="请输入安装位置" />
        </el-form-item>
        <el-form-item label="状态">
          <el-radio-group v-model="form.status">
            <el-radio
              v-for="dict in statusOptions"
              :key="dict.value"
              :label="dict.value"
            >{{dict.label}}</el-radio>
          </el-radio-group>
        </el-form-item>
        <el-form-item label="备注" prop="remark">
          <el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitForm">ç¡® å®š</el-button>
          <el-button @click="cancel">取 æ¶ˆ</el-button>
        </div>
      </template>
    </el-dialog>
    <!-- ç©ºè°ƒå®šæ—¶ä»»åŠ¡å¼¹çª— -->
    <el-dialog
      title="空调定时任务"
      v-model="scheduleDialogVisible"
      width="900px"
      append-to-body
    >
      <air-conditioner-schedule ref="scheduleDialogRef" />
    </el-dialog>
  </div>
</template>
<script setup name="AirConditionerList">
import { ref, reactive, onMounted, getCurrentInstance, nextTick } from 'vue'
import { Search, Refresh, Plus, Edit, Delete, Setting, Timer, ArrowDown } from '@element-plus/icons-vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { listAirConditioner, getAirConditioner, addAirConditioner, updateAirConditioner, delAirConditioner, controlAirConditioner } from "@/api/airconditioner/airconditioner"
import AirConditionerSchedule from './schedule.vue'
// èŽ·å–çˆ¶ç»„ä»¶å®žä¾‹
const { proxy } = getCurrentInstance()
const scheduleDialogRef = ref(null)
// é®ç½©å±‚
const loading = ref(true)
// é€‰ä¸­æ•°ç»„
const ids = ref([])
// éžå•个禁用
const single = ref(true)
// éžå¤šä¸ªç¦ç”¨
const multiple = ref(true)
// æ˜¾ç¤ºæœç´¢æ¡ä»¶
const showSearch = ref(true)
// æ€»æ¡æ•°
const total = ref(0)
// ç©ºè°ƒè¡¨æ ¼æ•°æ®
const airConditionerList = ref([])
// å¼¹å‡ºå±‚标题
const title = ref('')
// æ˜¯å¦æ˜¾ç¤ºå¼¹å‡ºå±‚
const open = ref(false)
// è¡¨å•参数
const form = ref({})
// è¡¨å•校验
const rules = {
  name: [
    { required: true, message: "空调名称不能为空", trigger: "blur" }
  ],
  controllerId: [
    { required: true, message: "控制器ID不能为空", trigger: "blur" }
  ],
  location: [
    { required: true, message: "安装位置不能为空", trigger: "blur" }
  ]
}
// çŠ¶æ€å­—å…¸
const statusOptions = [
  {
    label: "制冷",
    value: "0"
  },
  {
    label: "制热",
    value: "1"
  },
  {
    label: "关机",
    value: "2"
  }
]
// æŸ¥è¯¢å‚æ•°
const queryParams = ref({
  pageNum: 1,
  pageSize: 10,
  name: null,
  location: null,
  status: null
})
// è¡¨å•ref
const formRef = ref()
const queryFormRef = ref()
/** æŸ¥è¯¢ç©ºè°ƒåˆ—表 */
function getList() {
  loading.value = true
  listAirConditioner(queryParams.value).then(response => {
    airConditionerList.value = response.rows
    total.value = response.total
    loading.value = false
  })
}
// å–消按钮
function cancel() {
  open.value = false
  reset()
}
// è¡¨å•重置
function reset() {
  form.value = {
    id: null,
    name: null,
    controllerId: null,
    location: null,
    status: "2", // é»˜è®¤ä¸ºå…³æœºçŠ¶æ€
    remark: null
  }
  nextTick(() => {
    if (formRef.value) {
      formRef.value.resetFields()
    }
  })
}
/** æœç´¢æŒ‰é’®æ“ä½œ */
function handleQuery() {
  queryParams.value.pageNum = 1
  getList()
}
/** é‡ç½®æŒ‰é’®æ“ä½œ */
function resetQuery() {
  queryFormRef.value.resetFields()
  handleQuery()
}
// å¤šé€‰æ¡†é€‰ä¸­æ•°æ®
function handleSelectionChange(selection) {
  ids.value = selection.map(item => item.id)
  single.value = selection.length !== 1
  multiple.value = !selection.length
}
/** æ–°å¢žæŒ‰é’®æ“ä½œ */
function handleAdd() {
  reset()
  open.value = true
  title.value = "添加空调"
}
/** ä¿®æ”¹æŒ‰é’®æ“ä½œ */
function handleUpdate(row) {
  reset()
  const id = row.id || ids.value[0]
  getAirConditioner(id).then(response => {
    form.value = response.data
    open.value = true
    title.value = "修改空调"
  })
}
/** æäº¤æŒ‰é’® */
function submitForm() {
  formRef.value.validate(valid => {
    if (valid) {
      if (form.value.id != null) {
        updateAirConditioner(form.value).then(response => {
          ElMessage.success("修改成功")
          open.value = false
          getList()
        })
      } else {
        addAirConditioner(form.value).then(response => {
          ElMessage.success("新增成功")
          open.value = false
          getList()
        })
      }
    }
  })
}
/** åˆ é™¤æŒ‰é’®æ“ä½œ */
function handleDelete(row) {
  const deleteIds = row.id || ids.value
  ElMessageBox.confirm('是否确认删除空调编号为"' + deleteIds + '"的数据项?')
    .then(() => {
      return delAirConditioner(deleteIds)
    })
    .then(() => {
      getList()
      ElMessage.success("删除成功")
    })
    .catch(() => {})
}
/** æŽ§åˆ¶ç©ºè°ƒæ“ä½œ */
function handleControl(command, row) {
  const id = row.id
  const modeName = command === '0' ? '制冷开机' : command === '1' ? '制热开机' : '关机'
  ElMessageBox.confirm('是否确认对空调"' + row.name + '"执行' + modeName + '操作?')
    .then(() => {
      return controlAirConditioner(id, command, '0') // 0表示手动操作
    })
    .then(() => {
      // æ›´æ–°æœ¬åœ°çŠ¶æ€ï¼Œé¿å…ç­‰å¾…åˆ·æ–°
      row.status = command
      ElMessage.success(modeName + "操作成功")
      // åˆ·æ–°åˆ—表数据
      getList()
    })
    .catch(() => {})
}
/** å®šæ—¶ä»»åŠ¡æ“ä½œ */
function handleSchedule(row) {
  // è°ƒç”¨çˆ¶ç»„ä»¶çš„handleShowSchedule方法,传递空调ID
  proxy.$parent.handleShowSchedule(row.id)
}
// å®šæ—¶ä»»åŠ¡å¼¹çª—æ˜¾ç¤ºçŠ¶æ€
const scheduleDialogVisible = ref(false)
// å¤„理显示定时任务弹窗的方法(通过按钮点击)
function handleShowSchedule(airConditionerId) {
  scheduleDialogVisible.value = true
  // ç­‰å¾…DOM更新后调用定时任务组件的方法
  nextTick(() => {
    if (scheduleDialogRef.value) {
      scheduleDialogRef.value.loadScheduleByAirConditioner(airConditionerId)
    }
  })
}
onMounted(() => {
  getList()
})
// æš´éœ²æ–¹æ³•给父组件调用
defineExpose({
  getList
})
</script>
zhitan-vue/src/views/airconditioner/log.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,166 @@
<template>
  <div class="app-container">
    <el-form :model="queryParams" ref="queryFormRef" :inline="true" v-show="showSearch" label-width="68px">
      <el-form-item label="空调ID" prop="airConditionerId">
        <el-select v-model="queryParams.airConditionerId" placeholder="请选择空调" clearable>
          <el-option
            v-for="item in airConditionerOptions"
            :key="item.id"
            :label="item.name"
            :value="item.id"
          />
        </el-select>
      </el-form-item>
      <el-form-item label="操作类型" prop="operationType">
        <el-select v-model="queryParams.operationType" placeholder="请选择操作类型" clearable>
          <el-option
            v-for="dict in operationTypeOptions"
            :key="dict.value"
            :label="dict.label"
            :value="dict.value"
          />
        </el-select>
      </el-form-item>
      <el-form-item label="时间范围" prop="timeRange">
        <el-date-picker
          v-model="queryParams.timeRange"
          type="datetimerange"
          range-separator="至"
          start-placeholder="开始时间"
          end-placeholder="结束时间"
        />
      </el-form-item>
      <el-form-item>
        <el-button type="primary" :icon="Search" @click="handleQuery">搜索</el-button>
        <el-button :icon="Refresh" @click="resetQuery">重置</el-button>
      </el-form-item>
    </el-form>
    <el-table
      v-loading="loading"
      :data="logList"
      style="width: 100%"
    >
      <el-table-column type="index" width="50" align="center" />
      <el-table-column label="操作时间" align="center" prop="operateTime" width="160">
        <template #default="scope">
          <span>{{ parseTime(scope.row.operateTime) }}</span>
        </template>
      </el-table-column>
      <el-table-column label="空调信息" align="center" prop="airConditionerName" />
      <el-table-column label="操作类型" align="center" prop="operateType">
        <template #default="scope">
          <dict-tag :options="operationTypeOptions" :value="scope.row.operateType"/>
        </template>
      </el-table-column>
      <el-table-column label="工作模式" align="center" prop="operateMode" >
        <template #default="scope">
          <dict-tag :options="operationModeOptions" :value="scope.row.operateMode"/>
        </template>
      </el-table-column>
      <el-table-column label="操作结果" align="center" prop="operateResult">
        <template #default="scope">
          <el-tag :type="scope.row.operateResult === '1' ? 'success' : 'danger'">
            {{ scope.row.operateResult === '1' ? '成功' : '失败' }}
          </el-tag>
        </template>
      </el-table-column>
      <el-table-column label="操作人员" align="center" prop="operatorName" />
    </el-table>
    <pagination
      v-show="total > 0"
      :total="total"
      v-model:page="queryParams.pageNum"
      v-model:limit="queryParams.pageSize"
      @pagination="getList"
    />
  </div>
</template>
<script setup name="AirConditionerLog">
import { ref, reactive, onMounted, getCurrentInstance } from 'vue'
import { Search, Refresh } from '@element-plus/icons-vue'
import { parseTime } from '@/utils/ruoyi'
import { listAirConditioner } from '@/api/airconditioner/airconditioner'
import { listLog } from '@/api/airconditioner/log'
// èŽ·å–çˆ¶ç»„ä»¶å®žä¾‹
const { proxy } = getCurrentInstance()
// é®ç½©å±‚
const loading = ref(false)
// æ˜¾ç¤ºæœç´¢æ¡ä»¶
const showSearch = ref(true)
// æ€»æ¡æ•°
const total = ref(0)
// æ—¥å¿—表格数据
const logList = ref([])
// ç©ºè°ƒé€‰é¡¹
const airConditionerOptions = ref([])
// æ“ä½œç±»åž‹é€‰é¡¹
const operationTypeOptions = ref([
  { label: '手动', value: '0' },
  { label: '自动', value: '1' }
])
const operationModeOptions = ref([
  { label: '制冷', value: '0' },
  { label: '制热', value: '1' },
    { label: '关机', value: '2' },
])
// è¡¨å•ref
const queryFormRef = ref()
// æŸ¥è¯¢å‚æ•°
const queryParams = ref({
  pageNum: 1,
  pageSize: 10,
  airConditionerId: undefined,
  operationType: undefined,
  timeRange: []
})
/** æŸ¥è¯¢æ—¥å¿—列表 */
function getList() {
  loading.value = true
  listLog(queryParams.value).then(response => {
    logList.value = response.rows
    total.value = response.total
    loading.value = false
  })
}
/** æŸ¥è¯¢ç©ºè°ƒåˆ—表 */
function getAirConditioners() {
  listAirConditioner().then(response => {
    airConditionerOptions.value = response.rows
  })
}
/** æœç´¢æŒ‰é’®æ“ä½œ */
function handleQuery() {
  queryParams.value.pageNum = 1
  getList()
}
/** é‡ç½®æŒ‰é’®æ“ä½œ */
function resetQuery() {
  queryFormRef.value.resetFields()
  handleQuery()
}
onMounted(() => {
  getList()
  getAirConditioners()
  // èŽ·å–å­—å…¸æ•°æ®
//   proxy.getDicts('airconditioner_operation_type').then(response => {
//     operationTypeOptions.value = response.data
//   })
})
// æš´éœ²æ–¹æ³•给父组件调用
defineExpose({
  getList
})
</script>
zhitan-vue/src/views/airconditioner/schedule.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,434 @@
<template>
  <div class="app-container">
    <el-row :gutter="20">
      <el-col :span="24">
        <el-form :model="queryParams" ref="queryFormRef" :inline="true" v-show="showSearch" label-width="68px">
          <el-form-item label="状态" prop="status">
            <el-select v-model="queryParams.status" placeholder="定时任务状态" clearable>
              <el-option
                v-for="dict in statusOptions"
                :key="dict.dictValue"
                :label="dict.label"
                :value="dict.dictValue"
              />
            </el-select>
          </el-form-item>
          <el-form-item>
            <el-button type="primary" :icon="Search" @click="handleQuery">搜索</el-button>
            <el-button :icon="Refresh" @click="resetQuery">重置</el-button>
          </el-form-item>
        </el-form>
        <el-row :gutter="10" class="mb8">
          <el-col :span="1.5">
            <el-button
              type="primary"
              plain
              :icon="Plus"
              @click="handleAdd"
              v-hasPermi="['system:airconditioner:schedule:add']"
            >新增</el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button
              type="success"
              plain
              :icon="Edit"
              :disabled="single"
              @click="handleUpdate"
              v-hasPermi="['system:airconditioner:schedule:edit']"
            >修改</el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button
              type="danger"
              plain
              :icon="Delete"
              :disabled="multiple"
              @click="handleDelete"
              v-hasPermi="['system:airconditioner:schedule:remove']"
            >删除</el-button>
          </el-col>
          <right-toolbar v-model:show-search="showSearch" @queryTable="getList"></right-toolbar>
        </el-row>
        <el-table v-loading="loading" :data="scheduleList" @selection-change="handleSelectionChange">
          <el-table-column type="selection" width="55" align="center" />
          <el-table-column label="任务ID" align="center" prop="id" />
          <el-table-column label="空调名称" align="center" prop="airConditionerName">
            {{ currentAirConditionerName }}
            <!-- <template #default="scope">
              <span>{{ getAirConditionerName(scope.row.airConditionerId) }}</span>
            </template> -->
          </el-table-column>
          <el-table-column label="开始时间" align="center" prop="startTime" width="120">
<!--            <template #default="scope">-->
<!--              <span>{{ formatTimeOnly(scope.row.startTime) }}</span>-->
<!--            </template>-->
          </el-table-column>
          <el-table-column label="结束时间" align="center" prop="offTime" width="120">
<!--            <template #default="scope">-->
<!--              <span>{{ formatTimeOnly(scope.row.offTime) }}</span>-->
<!--            </template>-->
          </el-table-column>
          <el-table-column label="状态" align="center" prop="status">
            <template #default="scope">
              <dict-tag :options="statusOptions" :value="scope.row.status"/>
            </template>
          </el-table-column>
          <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
            <template #default="scope">
              <el-button
                type="primary"
                link
                :icon="Edit"
                @click="handleUpdate(scope.row)"
                v-hasPermi="['system:airconditioner:schedule:edit']"
              >修改</el-button>
              <el-button
                type="primary"
                link
                :icon="Delete"
                @click="handleDelete(scope.row)"
                v-hasPermi="['system:airconditioner:schedule:remove']"
              >删除</el-button>
            </template>
          </el-table-column>
        </el-table>
        <pagination
          v-show="total>0"
          :total="total"
          v-model:page="queryParams.pageNum"
          v-model:limit="queryParams.pageSize"
          @pagination="getList"
        />
      </el-col>
    </el-row>
    <!-- æ·»åŠ æˆ–ä¿®æ”¹ç©ºè°ƒå®šæ—¶ä»»åŠ¡å¯¹è¯æ¡† -->
    <el-dialog :title="title" v-model="open" width="500px" append-to-body>
      <el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
        <el-form-item label="空调" prop="airConditionerId">
          <el-select v-model="form.airConditionerId" placeholder="请选择空调" style="width: 100%" :disabled="true">
            <el-option
              v-for="item in airConditionerOptions"
              :key="item.id"
              :label="item.name"
              :value="item.id"
            />
          </el-select>
        </el-form-item>
        <el-form-item label="开始时间" prop="startTime">
          <el-time-picker
            v-model="form.startTime"
            format="HH:mm:ss"
            placeholder="选择开始时间"
            style="width: 100%"
          />
        </el-form-item>
        <el-form-item label="结束时间" prop="offTime">
          <el-time-picker
            v-model="form.offTime"
            format="HH:mm:ss"
            placeholder="选择结束时间"
            style="width: 100%"
          />
        </el-form-item>
        <el-form-item label="状态">
          <el-radio-group v-model="form.status">
            <el-radio
              v-for="dict in statusOptions"
              :key="dict.value"
              :label="dict.value"
            >{{dict.label}}</el-radio>
          </el-radio-group>
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitForm">ç¡® å®š</el-button>
          <el-button @click="cancel">取 æ¶ˆ</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>
<script setup name="AirConditionerSchedule">
import { ref, reactive, onMounted, getCurrentInstance, nextTick } from 'vue'
import { Search, Refresh, Plus, Edit, Delete } from '@element-plus/icons-vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { listSchedule, getSchedule, addSchedule, updateSchedule, delSchedule, getScheduleByAirConditionerId } from "@/api/airconditioner/schedule"
import { listAirConditioner } from "@/api/airconditioner/airconditioner"
import { parseTime } from '@/utils/ruoyi'
/** æ ¼å¼åŒ–时间为时:分:秒 */
function formatTimeOnly(time) {
  if (!time) return ''
  const date = new Date(time)
  return `${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}:${date.getSeconds().toString().padStart(2, '0')}`
}
// èŽ·å–çˆ¶ç»„ä»¶å®žä¾‹
const { proxy } = getCurrentInstance()
// é®ç½©å±‚
const loading = ref(true)
// é€‰ä¸­æ•°ç»„
const ids = ref([])
// éžå•个禁用
const single = ref(true)
// éžå¤šä¸ªç¦ç”¨
const multiple = ref(true)
// æ˜¾ç¤ºæœç´¢æ¡ä»¶
const showSearch = ref(true)
// æ€»æ¡æ•°
const total = ref(0)
// å®šæ—¶ä»»åŠ¡è¡¨æ ¼æ•°æ®
const scheduleList = ref([])
// ç©ºè°ƒé€‰é¡¹
const airConditionerOptions = ref([])
// å½“前选中的空调ID
const currentAirConditionerId = ref(null)
const currentAirConditionerName= ref('')
// å¼¹å‡ºå±‚标题
const title = ref('')
// æ˜¯å¦æ˜¾ç¤ºå¼¹å‡ºå±‚
const open = ref(false)
// è¡¨å•参数
const form = ref({})
// è¡¨å•ref
const formRef = ref()
const queryFormRef = ref()
// çŠ¶æ€å­—å…¸
const statusOptions = [
  {
    label: "正常",
    value: "0"
  },
  {
    label: "停用",
    value: "1"
  }
]
// æŸ¥è¯¢å‚æ•°
const queryParams = ref({
  pageNum: 1,
  pageSize: 10,
  airConditionerId: null,
  status: null
})
// è¡¨å•校验
const rules = {
  airConditionerId: [
    { required: true, message: "空调不能为空", trigger: "change" }
  ],
  startTime: [
    { required: true, message: "开始时间不能为空", trigger: "blur" }
  ],
  offTime: [
    { required: true, message: "结束时间不能为空", trigger: "blur" }
  ]
}
/** æŸ¥è¯¢å®šæ—¶ä»»åŠ¡åˆ—è¡¨ */
function getList() {
  loading.value = true
  // å¦‚果有当前选中的空调ID,则只查询该空调的定时任务
  if (currentAirConditionerId.value) {
    getScheduleByAirConditionerId(currentAirConditionerId.value).then(response => {
      scheduleList.value = response.data.sort((a, b) => {
        return new Date(a.startTime) - new Date(b.startTime)
      })
      total.value = response.data.length
      loading.value = false
    })
  } else {
    // å…¼å®¹åŽŸæœ‰é€»è¾‘ï¼Œä½†æ­£å¸¸æƒ…å†µä¸‹ä¸ä¼šæ‰§è¡Œåˆ°è¿™é‡Œ
    listSchedule(queryParams.value).then(response => {
      // å…ˆæŒ‰ç©ºè°ƒåç§°æŽ’序,再按开始时间排序
      scheduleList.value = response.rows.sort((a, b) => {
        const nameA = getAirConditionerName(a.airConditionerId)
        const nameB = getAirConditionerName(b.airConditionerId)
        if (nameA !== nameB) {
          return nameA.localeCompare(nameB)
        }
        return new Date(a.startTime) - new Date(b.startTime)
      })
      total.value = response.total
      loading.value = false
    })
  }
}
/** æŸ¥è¯¢ç©ºè°ƒåˆ—表 */
function getAirConditioners() {
  listAirConditioner().then(response => {
    airConditionerOptions.value = response.rows
  })
}
/** æ ¹æ®ç©ºè°ƒID获取空调名称 */
// function getAirConditionerName(airConditionerId) {
//   const airConditioner = airConditionerOptions.value.find(item => item.id === airConditionerId)
//   return airConditioner ? airConditioner.name : ''
// }
// å–消按钮
function cancel() {
  open.value = false
  reset()
}
// è¡¨å•重置
function reset() {
  form.value = {
    id: null,
    airConditionerId: null,
    startTime: null,
    offTime: null,
    status: "0"
  }
  nextTick(() => {
    if (formRef.value) {
      formRef.value.resetFields()
    }
  })
}
/** æœç´¢æŒ‰é’®æ“ä½œ */
function handleQuery() {
  queryParams.value.pageNum = 1
  getList()
}
/** é‡ç½®æŒ‰é’®æ“ä½œ */
function resetQuery() {
  queryFormRef.value.resetFields()
  handleQuery()
}
// å¤šé€‰æ¡†é€‰ä¸­æ•°æ®
function handleSelectionChange(selection) {
  ids.value = selection.map(item => item.id)
  single.value = selection.length !== 1
  multiple.value = !selection.length
}
/** æ–°å¢žæŒ‰é’®æ“ä½œ */
function handleAdd() {
  reset()
  // è®¾ç½®å½“前选中的空调ID
  form.value.airConditionerId = currentAirConditionerId.value
  open.value = true
  title.value = "添加定时任务"
}
/** ä¿®æ”¹æŒ‰é’®æ“ä½œ */
function handleUpdate(row) {
  reset()
  const id = row.id || ids.value[0]
  getSchedule(id).then(response => {
    const data = response.data
    // å¤„理时间格式,将HH:MM:SS格式转换为Date对象
    if (data.startTime) {
      const [hours, minutes, seconds] = data.startTime.split(':').map(Number)
      const startTime = new Date()
      startTime.setHours(hours, minutes, seconds)
      data.startTime = startTime
    }
    if (data.offTime) {
      const [hours, minutes, seconds] = data.offTime.split(':').map(Number)
      const offTime = new Date()
      offTime.setHours(hours, minutes, seconds)
      data.offTime = offTime
    }
    form.value = data
    open.value = true
    title.value = "修改定时任务"
  })
}
/** æäº¤æŒ‰é’® */
function submitForm() {
  formRef.value.validate(valid => {
    if (valid) {
      // åˆ›å»ºæäº¤æ•°æ®çš„副本
      const submitData = { ...form.value }
      // å¤„理时间格式,只保留时分秒
      if (submitData.startTime) {
        const startTime = new Date(submitData.startTime)
        submitData.startTime = `${startTime.getHours().toString().padStart(2, '0')}:${startTime.getMinutes().toString().padStart(2, '0')}:${startTime.getSeconds().toString().padStart(2, '0')}`
      }
      if (submitData.offTime) {
        const offTime = new Date(submitData.offTime)
        submitData.offTime = `${offTime.getHours().toString().padStart(2, '0')}:${offTime.getMinutes().toString().padStart(2, '0')}:${offTime.getSeconds().toString().padStart(2, '0')}`
      }
      if (submitData.id != null) {
        updateSchedule(submitData).then(response => {
          ElMessage.success("修改成功")
          open.value = false
          getList()
        })
      } else {
        addSchedule(submitData).then(response => {
          ElMessage.success("新增成功")
          open.value = false
          getList()
        })
      }
    }
  })
}
/** åˆ é™¤æŒ‰é’®æ“ä½œ */
function handleDelete(row) {
  const deleteIds = row.id || ids.value
  ElMessageBox.confirm('是否确认删除定时任务编号为"' + deleteIds + '"的数据项?')
    .then(() => {
      return delSchedule(deleteIds)
    })
    .then(() => {
      getList()
      ElMessage.success("删除成功")
    })
    .catch(() => {})
}
/** æ ¹æ®ç©ºè°ƒID加载定时任务 */
function loadScheduleByAirConditioner(airConditioner) {
  console.log('loadScheduleByAirConditioner', airConditioner)
  if (airConditioner) {
    currentAirConditionerName.value = airConditioner.name
    // è®¾ç½®å½“前选中的空调ID
    currentAirConditionerId.value = airConditioner.id
    queryParams.value.airConditionerId = airConditioner.controllerId
    handleQuery()
  }
}
onMounted(() => {
  // é¡µé¢åŠ è½½æ—¶å…ˆèŽ·å–ç©ºè°ƒåˆ—è¡¨ï¼Œä½†ä¸è‡ªåŠ¨åŠ è½½å®šæ—¶ä»»åŠ¡åˆ—è¡¨
  // å®šæ—¶ä»»åŠ¡åˆ—è¡¨å°†é€šè¿‡çˆ¶ç»„ä»¶ä¼ é€’çš„ç©ºè°ƒID加载
  getAirConditioners()
})
// æš´éœ²æ–¹æ³•给父组件调用
defineExpose({
  getList,
  loadScheduleByAirConditioner
})
</script>