diff --git a/src/main/java/com/iflytop/colortitration/app/controller/SystemController.java b/src/main/java/com/iflytop/colortitration/app/controller/SystemController.java new file mode 100644 index 0000000..cc74a69 --- /dev/null +++ b/src/main/java/com/iflytop/colortitration/app/controller/SystemController.java @@ -0,0 +1,44 @@ +package com.iflytop.colortitration.app.controller; + +import com.iflytop.colortitration.app.core.state.DeviceState; +import com.iflytop.colortitration.app.model.dto.TimeSetDTO; +import com.iflytop.colortitration.app.service.SystemService; +import com.iflytop.colortitration.common.result.Result; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +import java.time.Instant; + +@Tag(name = "系统") +@RestController +@RequestMapping("/api/sys") +@RequiredArgsConstructor +@Slf4j +public class SystemController { + private final SystemService systemService; + private final DeviceState deviceState; + + @Operation(summary = "获取当前系统状态") + @GetMapping("/device-status") + public Result getDeviceStatus() { + return Result.success(deviceState.toJSON()); + } + + @Operation(summary = "设置系统时间") + @PostMapping("/set-datetime") + public Result setDatetime(@Valid @RequestBody TimeSetDTO timeSetDTO) { + systemService.setSystemTime(timeSetDTO.getEpochMilli()); + return Result.success(); + } + + @Operation(summary = "获取当前系统时间") + @GetMapping("/datetime") + public Result getDatetime() { + return Result.success(Instant.now().toEpochMilli()); + } + +} diff --git a/src/main/java/com/iflytop/colortitration/app/model/dto/TimeSetDTO.java b/src/main/java/com/iflytop/colortitration/app/model/dto/TimeSetDTO.java new file mode 100644 index 0000000..6ee5f2a --- /dev/null +++ b/src/main/java/com/iflytop/colortitration/app/model/dto/TimeSetDTO.java @@ -0,0 +1,16 @@ +package com.iflytop.colortitration.app.model.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Data +public class TimeSetDTO { + /** + * 要设置的时间戳(毫秒),UTC 纪元毫秒数 + */ + @NotNull + @Schema(description = "要设置的时间戳(毫秒),UTC 纪元毫秒数") + private Long epochMilli; + +} diff --git a/src/main/java/com/iflytop/colortitration/app/service/SystemService.java b/src/main/java/com/iflytop/colortitration/app/service/SystemService.java new file mode 100644 index 0000000..745a3b0 --- /dev/null +++ b/src/main/java/com/iflytop/colortitration/app/service/SystemService.java @@ -0,0 +1,27 @@ +package com.iflytop.colortitration.app.service; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.iflytop.colortitration.common.mapper.SystemConfigMapper; +import com.iflytop.colortitration.common.model.entity.SystemConfig; +import com.iflytop.colortitration.common.utils.CommandUtil; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +/** + * 系统配置接口服务 + */ +@Service +@RequiredArgsConstructor +public class SystemService extends ServiceImpl { + /** + * 将系统时区(可选)和系统时间(必选)一起设定 + * + * @param epochMilli UTC 毫秒时间戳 + */ + public void setSystemTime(long epochMilli) { + long epochSecond = epochMilli / 1_000; + CommandUtil.runCommand("timedatectl", "set-ntp", "false"); + CommandUtil.runCommand("date", "-s", "@" + epochSecond); + CommandUtil.runCommand("hwclock", "--systohc"); + } +} \ No newline at end of file diff --git a/src/main/java/com/iflytop/colortitration/common/service/SystemConfigService.java b/src/main/java/com/iflytop/colortitration/common/service/SystemConfigService.java index 292cf3a..452ad1b 100644 --- a/src/main/java/com/iflytop/colortitration/common/service/SystemConfigService.java +++ b/src/main/java/com/iflytop/colortitration/common/service/SystemConfigService.java @@ -1,8 +1,8 @@ package com.iflytop.colortitration.common.service; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; -import com.iflytop.colortitration.common.model.entity.SystemConfig; import com.iflytop.colortitration.common.mapper.SystemConfigMapper; +import com.iflytop.colortitration.common.model.entity.SystemConfig; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -11,6 +11,6 @@ import org.springframework.stereotype.Service; */ @Service @RequiredArgsConstructor -public class SystemConfigService extends ServiceImpl { +public class SystemConfigService extends ServiceImpl { } \ No newline at end of file diff --git a/src/main/java/com/iflytop/colortitration/common/utils/CommandUtil.java b/src/main/java/com/iflytop/colortitration/common/utils/CommandUtil.java new file mode 100644 index 0000000..98b7aaf --- /dev/null +++ b/src/main/java/com/iflytop/colortitration/common/utils/CommandUtil.java @@ -0,0 +1,56 @@ +package com.iflytop.colortitration.common.utils; + +import lombok.extern.slf4j.Slf4j; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; + +/** + * 系统命令执行工具类 + */ +@Slf4j +public class CommandUtil { + + private static final String DEFAULT_PATH = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"; + + /** + * 执行系统命令并返回输出内容 + * + * @param cmd 命令及其参数,如 runCommand("ls", "-l") + * @return 命令输出 + * @throws RuntimeException 命令执行失败或中断 + */ + public static String runCommand(String... cmd) { + try { + log.info("CMD: {}", String.join(" ", cmd)); + ProcessBuilder pb = new ProcessBuilder(cmd); + pb.environment().put("PATH", DEFAULT_PATH); // 设置 PATH 变量,防止找不到命令 + pb.redirectErrorStream(true); // 合并错误输出 + + Process process = pb.start(); + StringBuilder output = new StringBuilder(); + + try (BufferedReader reader = new BufferedReader( + new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8))) { + String line; + while ((line = reader.readLine()) != null) { + output.append(line).append('\n'); + } + } + + int exitCode = process.waitFor(); + if (exitCode != 0) { + throw new IllegalStateException(String.format( + "命令执行失败: [%s], 退出码=%d, 输出=\n%s", + String.join(" ", cmd), exitCode, output)); + } + + return output.toString().trim(); // 去掉最后一个换行 + } catch (IOException | InterruptedException e) { + Thread.currentThread().interrupt(); // 恢复中断状态 + throw new RuntimeException("执行系统命令失败: " + String.join(" ", cmd), e); + } + } +} \ No newline at end of file