Merge remote-tracking branch 'origin/V1.00' into V1.00

This commit is contained in:
waner 2026-03-13 17:31:22 +08:00
commit c1e91e76b0
3 changed files with 180 additions and 0 deletions

View File

@ -0,0 +1,33 @@
package com.cisd.tms.modules.device.controller;
import com.cisd.tms.common.api.ApiResponse;
import com.cisd.tms.common.enums.ErrorCode;
import com.cisd.tms.modules.device.dto.TimeConfigRequest;
import com.cisd.tms.modules.device.service.TimeConfigService;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/v1/device")
@Tag(name = "device-time-config-controller")
public class TimeConfigController {
private final TimeConfigService timeConfigService;
public TimeConfigController(TimeConfigService timeConfigService) {
this.timeConfigService = timeConfigService;
}
@PostMapping("/time-config")
public ApiResponse<Void> configureTime(@RequestBody TimeConfigRequest request) {
//todo 鉴权
timeConfigService.processTimeConfig(request);
return ApiResponse.success();
}
}

View File

@ -0,0 +1,35 @@
package com.cisd.tms.modules.device.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
@Data
@Schema(description = "设备时间配置请求参数")
public class TimeConfigRequest {
@Schema(description = "配置模式",
allowableValues = {"MANUAL", "NTP"},
example = "MANUAL",
requiredMode = Schema.RequiredMode.REQUIRED)
private String mode;
@Schema(description = "手工调整输入时间 (仅 MANUAL 模式下必填,格式: yyyy-MM-dd HH:mm)",
example = "2026-01-30 00:00")
private String datetime;
@Schema(description = "系统时区标识",
example = "Asia/Shanghai")
private String timezone;
@Schema(description = "NTP服务器地址列表 (仅 NTP 模式下必填)",
example = "[\"ntp.aliyun.com\", \"ntp.tencent.com\"]")
private List<String> ntpServers;
@Schema(description = "NTP同步间隔时间 (单位: 秒。默认 600)",
example = "600")
private Integer syncInterval;
}

View File

@ -0,0 +1,112 @@
package com.cisd.tms.modules.device.service;
import com.cisd.tms.modules.device.dto.TimeConfigRequest;
import org.springframework.stereotype.Service;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.List;
@Service
public class TimeConfigService {
public void processTimeConfig(TimeConfigRequest request) {
String mode = request.getMode();
if ("MANUAL".equalsIgnoreCase(mode)) {
String timezone = request.getTimezone();
if (timezone == null || !timezone.matches("^[A-Za-z0-9/_-]+$")) {
throw new IllegalArgumentException("Invalid or empty timezone format");
}
String datetime = request.getDatetime();
// 假设格式必须为 YYYY-MM-DD HH:MM
if (datetime == null || !datetime.matches("^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}$")) {
throw new IllegalArgumentException("Invalid or empty datetime format. Expected: YYYY-MM-DD HH:MM");
}
executeCommand("timedatectl set-timezone " + request.getTimezone().trim());
executeCommand("systemctl stop chronyd");
executeCommand("date -s '" + request.getDatetime() + "'");
} else if ("NTP".equalsIgnoreCase(mode)) {
List<String> servers = request.getNtpServers();
if (servers == null || servers.isEmpty()) {
throw new IllegalArgumentException("At least one NTP server IP must be provided in NTP mode");
}
int interval = request.getSyncInterval() != null ? request.getSyncInterval() : 600;
int pollExp = (int) Math.round(Math.log(interval) / Math.log(2));
StringBuilder serverConfLines = new StringBuilder();
for (String server : servers) {
if (!server.matches("^[A-Za-z0-9.-]+$")) {
throw new IllegalArgumentException("Invalid NTP server address format: " + server);
}
String pingResult = executeCommand("ping -c 1 -W 1 " + server);
if (!pingResult.contains("1 received")) {
throw new RuntimeException("NTP server is unreachable via ping: " + server);
}
serverConfLines.append("server ").append(server)
.append(" iburst minpoll ").append(pollExp)
.append(" maxpoll ").append(pollExp).append("\\n");
}
String confContent = serverConfLines +
"driftfile /var/lib/chrony/drift\\n" +
"makestep 1.0 3\\n" +
"rtcsync\\n";
executeCommand("echo -e '" + confContent + "' > /etc/chrony.conf");
executeCommand("systemctl restart chronyd");
executeCommand("chronyc -a makestep");
String status = executeCommand("chronyc tracking");
if (!status.contains("Reference ID")) {
throw new RuntimeException("NTP sync status check failed");
}
} else {
throw new IllegalArgumentException("Unknown mode: " + mode);
}
}
private String executeCommand(String command){
ProcessBuilder processBuilder = new ProcessBuilder("bash", "-c", command);
processBuilder.redirectErrorStream(true);
Process process = null;
try {
process = processBuilder.start();
StringBuilder output = new StringBuilder();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
output.append(line).append("\n");
}
}
int exitCode = process.waitFor();
if (exitCode != 0) {
throw new RuntimeException("command execution failed: [" + command + "] ");
}
return output.toString();
} catch (IOException e){
throw new RuntimeException("I/O error executing command: [" + command + "]", e);
} catch (InterruptedException e) {
if (process != null) {
process.destroy();
}
Thread.currentThread().interrupt();
throw new RuntimeException("process was interrupted while executing command: [" + command + "]", e);
}
}
}