commit 9ceba6f9e35031e2185a7b90730c47e8d99cac97 Author: liejiu946 Date: Thu Apr 23 14:23:42 2026 +0800 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d32f38a --- /dev/null +++ b/.gitignore @@ -0,0 +1,66 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store +/.idea/ + +# ---> Java +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* +replay_pid* + diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..a242ad9 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..63e9001 --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..8d66637 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 0000000..712ab9d --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..67e1e61 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..288b36b --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..e15432c --- /dev/null +++ b/README.md @@ -0,0 +1,42 @@ +# 云端 OTA Server + +这是一个基于 `Spring Boot 3.2.1 + Maven + JDK 17 + MySQL + MyBatis-Plus` 的最小可用云端 OTA 管理服务,面向“云端发布、车端检测、安卓提示、人工确认、Docker 执行”的半自动升级模式。 + +## 当前能力 +- 发布 OTA 版本清单 +- 注册车辆 +- 为指定车辆分配可见版本 +- 提供 Agent 心跳、检查更新、结果上报接口 +- 维护任务状态:`AVAILABLE`、`WAITING_CONFIRM`、`UPGRADING`、`SUCCESS`、`FAILED`、`ROLLED_BACK`、`SKIPPED` +- 数据持久化到 MySQL + +## 启动前准备 +1. 安装 JDK 17、Maven、MySQL 8.x +2. 创建数据库并执行初始化脚本:`src/main/resources/schema.sql` +3. 按实际环境修改 `src/main/resources/application.yml` 中的数据库连接 + +## 启动方式 +1. 在项目根目录运行: + `mvn spring-boot:run` +2. 默认端口:`8080` + +## 鉴权 +Agent 侧接口要求请求头: +- `X-OTA-TOKEN: dev-token` + +可在 `src/main/resources/application.yml` 中修改。 + +## 管理端接口 +- `POST /api/admin/releases` +- `GET /api/admin/releases` +- `POST /api/admin/vehicles` +- `GET /api/admin/vehicles` +- `POST /api/admin/assignments` +- `GET /api/admin/assignments` + +## Agent 接口 +- `POST /api/agent/heartbeat` +- `POST /api/agent/update-check` +- `POST /api/agent/report` +- `POST /api/agent/confirm` +- `POST /api/agent/postpone` diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..1ee12b8 --- /dev/null +++ b/pom.xml @@ -0,0 +1,80 @@ + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 3.2.1 + + + + com.noblelift + ota-server + 0.0.1-SNAPSHOT + ota-server + Cloud OTA management platform for vehicle docker upgrades + + + 17 + 3.5.5 + 1.39.0 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-validation + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.boot + spring-boot-starter-aop + + + cn.dev33 + sa-token-spring-boot3-starter + ${sa-token.version} + + + com.baomidou + mybatis-plus-spring-boot3-starter + ${mybatis-plus.version} + + + com.mysql + mysql-connector-j + runtime + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/src/main/java/com/noblelift/ota/OtaServerApplication.java b/src/main/java/com/noblelift/ota/OtaServerApplication.java new file mode 100644 index 0000000..0513322 --- /dev/null +++ b/src/main/java/com/noblelift/ota/OtaServerApplication.java @@ -0,0 +1,12 @@ +package com.noblelift.ota; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class OtaServerApplication { + + public static void main(String[] args) { + SpringApplication.run(OtaServerApplication.class, args); + } +} diff --git a/src/main/java/com/noblelift/ota/api/GlobalExceptionHandler.java b/src/main/java/com/noblelift/ota/api/GlobalExceptionHandler.java new file mode 100644 index 0000000..1ab1913 --- /dev/null +++ b/src/main/java/com/noblelift/ota/api/GlobalExceptionHandler.java @@ -0,0 +1,45 @@ +package com.noblelift.ota.api; + +import com.noblelift.ota.common.dto.ApiMessage; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import java.time.Instant; + +@RestControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(IllegalArgumentException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ApiMessage handleIllegalArgument(IllegalArgumentException ex) { + return new ApiMessage("BAD_REQUEST", ex.getMessage(), Instant.now()); + } + + @ExceptionHandler(DataIntegrityViolationException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ApiMessage handleDataIntegrityViolation(DataIntegrityViolationException ex) { + String message = ex.getMostSpecificCause() == null ? ex.getMessage() : ex.getMostSpecificCause().getMessage(); + if (message != null && message.contains("fk_assignment_release_version")) { + message = "该版本已被任务引用,无法删除或修改版本号"; + } else if (message != null && message.contains("fk_assignment_vehicle_id")) { + message = "该车辆存在任务记录,无法删除或修改车辆 ID"; + } else { + message = "数据被关联引用,当前操作无法完成"; + } + return new ApiMessage("BAD_REQUEST", message, Instant.now()); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ApiMessage handleValidation(MethodArgumentNotValidException ex) { + String message = ex.getBindingResult().getFieldErrors().stream() + .findFirst() + .map(error -> error.getField() + " " + error.getDefaultMessage()) + .orElse("Validation failed"); + return new ApiMessage("VALIDATION_ERROR", message, Instant.now()); + } +} diff --git a/src/main/java/com/noblelift/ota/common/annotation/Log.java b/src/main/java/com/noblelift/ota/common/annotation/Log.java new file mode 100644 index 0000000..58c4089 --- /dev/null +++ b/src/main/java/com/noblelift/ota/common/annotation/Log.java @@ -0,0 +1,13 @@ +package com.noblelift.ota.common.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Log { + + String value() default ""; +} diff --git a/src/main/java/com/noblelift/ota/common/aspect/LogAspect.java b/src/main/java/com/noblelift/ota/common/aspect/LogAspect.java new file mode 100644 index 0000000..8e7ee14 --- /dev/null +++ b/src/main/java/com/noblelift/ota/common/aspect/LogAspect.java @@ -0,0 +1,61 @@ +package com.noblelift.ota.common.aspect; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.noblelift.ota.common.annotation.Log; +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import java.util.Arrays; + +@Aspect +@Component +@Slf4j +@RequiredArgsConstructor +public class LogAspect { + + private final ObjectMapper objectMapper; + + @Around("@annotation(logAnnotation)") + public Object around(ProceedingJoinPoint joinPoint, Log logAnnotation) throws Throwable { + long start = System.currentTimeMillis(); + MethodSignature signature = (MethodSignature) joinPoint.getSignature(); + RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); + HttpServletRequest request = requestAttributes instanceof ServletRequestAttributes servletRequestAttributes + ? servletRequestAttributes.getRequest() + : null; + String requestUri = request == null ? "N/A" : request.getRequestURI(); + String requestMethod = request == null ? "N/A" : request.getMethod(); + String params = serializeArgs(joinPoint.getArgs()); + String operation = logAnnotation.value().isBlank() ? signature.getMethod().getName() : logAnnotation.value(); + log.info("[API-REQUEST] operation={}, method={}, uri={}, params={}", operation, requestMethod, requestUri, params); + try { + Object result = joinPoint.proceed(); + log.info("[API-RESPONSE] operation={}, duration={}ms", operation, System.currentTimeMillis() - start); + return result; + } catch (Throwable ex) { + log.error("[API-ERROR] operation={}, duration={}ms, message={}", operation, System.currentTimeMillis() - start, ex.getMessage(), ex); + throw ex; + } + } + + private String serializeArgs(Object[] args) { + Object[] filtered = Arrays.stream(args) + .filter(arg -> !(arg instanceof HttpServletRequest)) + .toArray(); + try { + return objectMapper.writeValueAsString(filtered); + } catch (JsonProcessingException e) { + return Arrays.toString(filtered); + } + } +} diff --git a/src/main/java/com/noblelift/ota/common/dto/ApiMessage.java b/src/main/java/com/noblelift/ota/common/dto/ApiMessage.java new file mode 100644 index 0000000..a8eb4e0 --- /dev/null +++ b/src/main/java/com/noblelift/ota/common/dto/ApiMessage.java @@ -0,0 +1,19 @@ +package com.noblelift.ota.common.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.Instant; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ApiMessage { + + private String code; + private String message; + private Instant timestamp; +} diff --git a/src/main/java/com/noblelift/ota/common/model/ReleaseManifest.java b/src/main/java/com/noblelift/ota/common/model/ReleaseManifest.java new file mode 100644 index 0000000..8b9ab28 --- /dev/null +++ b/src/main/java/com/noblelift/ota/common/model/ReleaseManifest.java @@ -0,0 +1,24 @@ +package com.noblelift.ota.common.model; + +import com.noblelift.ota.domain.UpgradeMode; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.Instant; +import java.util.Map; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ReleaseManifest { + + private String releaseVersion; + private String releaseNotes; + private UpgradeMode upgradeMode; + private Instant publishedAt; + private Boolean parkingRequired; + private Map components; +} diff --git a/src/main/java/com/noblelift/ota/common/model/VehicleAssignment.java b/src/main/java/com/noblelift/ota/common/model/VehicleAssignment.java new file mode 100644 index 0000000..332a785 --- /dev/null +++ b/src/main/java/com/noblelift/ota/common/model/VehicleAssignment.java @@ -0,0 +1,26 @@ +package com.noblelift.ota.common.model; + +import com.noblelift.ota.domain.TaskStatus; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.Instant; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class VehicleAssignment { + + private String vehicleId; + private String releaseVersion; + private TaskStatus taskStatus; + private Instant promptedAt; + private Instant confirmedAt; + private Instant startedAt; + private Instant finishedAt; + private Integer postponeCount; + private String lastMessage; +} diff --git a/src/main/java/com/noblelift/ota/common/model/VehicleInfo.java b/src/main/java/com/noblelift/ota/common/model/VehicleInfo.java new file mode 100644 index 0000000..015463b --- /dev/null +++ b/src/main/java/com/noblelift/ota/common/model/VehicleInfo.java @@ -0,0 +1,31 @@ +package com.noblelift.ota.common.model; + +import com.noblelift.ota.domain.AgentStatus; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.Instant; +import java.util.Map; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class VehicleInfo { + + private String vehicleId; + private String vin; + private String currentRelease; + private String currentBackendVersion; + private String currentFrontendVersion; + private String currentRosVersion; + private Instant lastSeenAt; + private AgentStatus agentStatus; + private String targetRelease; + private String lastResult; + private Map images; + private String backupFile; + private Boolean online; +} diff --git a/src/main/java/com/noblelift/ota/common/util/ReleaseVersionComparator.java b/src/main/java/com/noblelift/ota/common/util/ReleaseVersionComparator.java new file mode 100644 index 0000000..bcc837b --- /dev/null +++ b/src/main/java/com/noblelift/ota/common/util/ReleaseVersionComparator.java @@ -0,0 +1,42 @@ +package com.noblelift.ota.common.util; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public final class ReleaseVersionComparator { + + private static final Pattern TOKEN_PATTERN = Pattern.compile("\\d+"); + + private ReleaseVersionComparator() { + } + + public static int compare(String left, String right) { + List leftTokens = tokenize(left); + List rightTokens = tokenize(right); + int size = Math.max(leftTokens.size(), rightTokens.size()); + for (int i = 0; i < size; i++) { + String leftToken = i < leftTokens.size() ? leftTokens.get(i) : "0"; + String rightToken = i < rightTokens.size() ? rightTokens.get(i) : "0"; + int result = new BigInteger(leftToken).compareTo(new BigInteger(rightToken)); + if (result != 0) { + return result; + } + } + return 0; + } + + private static List tokenize(String value) { + List tokens = new ArrayList<>(); + if (value == null || value.isBlank()) { + return tokens; + } + Matcher matcher = TOKEN_PATTERN.matcher(value); + while (matcher.find()) { + tokens.add(matcher.group()); + } + return tokens; + } +} diff --git a/src/main/java/com/noblelift/ota/config/AgentTokenAuthenticator.java b/src/main/java/com/noblelift/ota/config/AgentTokenAuthenticator.java new file mode 100644 index 0000000..258e47d --- /dev/null +++ b/src/main/java/com/noblelift/ota/config/AgentTokenAuthenticator.java @@ -0,0 +1,52 @@ +package com.noblelift.ota.config; + +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Component; +import org.springframework.web.server.ResponseStatusException; + +@Component +public class AgentTokenAuthenticator { + + private static final String TOKEN_HEADER = "X-OTA-TOKEN"; + private static final String AUTHORIZATION_HEADER = "Authorization"; + private static final String BEARER_PREFIX = "Bearer "; + + private final OtaProperties otaProperties; + + public AgentTokenAuthenticator(OtaProperties otaProperties) { + this.otaProperties = otaProperties; + } + + public void verify(HttpServletRequest request) { + String expectedToken = trimToNull(otaProperties.getAuthToken()); + if (expectedToken == null) { + throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "OTA agent token is not configured"); + } + + String providedToken = extractToken(request); + if (!expectedToken.equals(providedToken)) { + throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Invalid OTA agent token"); + } + } + + private String extractToken(HttpServletRequest request) { + String token = trimToNull(request.getHeader(TOKEN_HEADER)); + if (token != null) { + return token; + } + String authorization = trimToNull(request.getHeader(AUTHORIZATION_HEADER)); + if (authorization != null && authorization.regionMatches(true, 0, BEARER_PREFIX, 0, BEARER_PREFIX.length())) { + return trimToNull(authorization.substring(BEARER_PREFIX.length())); + } + return null; + } + + private String trimToNull(String value) { + if (value == null) { + return null; + } + String trimmed = value.trim(); + return trimmed.isEmpty() ? null : trimmed; + } +} diff --git a/src/main/java/com/noblelift/ota/config/OtaConfiguration.java b/src/main/java/com/noblelift/ota/config/OtaConfiguration.java new file mode 100644 index 0000000..fb7383e --- /dev/null +++ b/src/main/java/com/noblelift/ota/config/OtaConfiguration.java @@ -0,0 +1,26 @@ +package com.noblelift.ota.config; + +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import org.springframework.web.filter.CorsFilter; + +@Configuration +@EnableConfigurationProperties(OtaProperties.class) +public class OtaConfiguration { + + @Bean + public CorsFilter corsFilter(OtaProperties otaProperties) { + CorsConfiguration configuration = new CorsConfiguration(); + configuration.setAllowedOrigins(otaProperties.getCors().getAllowedOrigins()); + configuration.setAllowedMethods(otaProperties.getCors().getAllowedMethods()); + configuration.setAllowedHeaders(otaProperties.getCors().getAllowedHeaders()); + configuration.setAllowCredentials(otaProperties.getCors().isAllowCredentials()); + + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", configuration); + return new CorsFilter(source); + } +} diff --git a/src/main/java/com/noblelift/ota/config/OtaProperties.java b/src/main/java/com/noblelift/ota/config/OtaProperties.java new file mode 100644 index 0000000..f2e9536 --- /dev/null +++ b/src/main/java/com/noblelift/ota/config/OtaProperties.java @@ -0,0 +1,77 @@ +package com.noblelift.ota.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +import java.util.ArrayList; +import java.util.List; + +@ConfigurationProperties(prefix = "ota") +public class OtaProperties { + + private String authToken = "dev-token"; + private long heartbeatTimeoutSeconds = 120; + private Cors cors = new Cors(); + + public String getAuthToken() { + return authToken; + } + + public void setAuthToken(String authToken) { + this.authToken = authToken; + } + + public long getHeartbeatTimeoutSeconds() { + return heartbeatTimeoutSeconds; + } + + public void setHeartbeatTimeoutSeconds(long heartbeatTimeoutSeconds) { + this.heartbeatTimeoutSeconds = heartbeatTimeoutSeconds; + } + + public Cors getCors() { + return cors; + } + + public void setCors(Cors cors) { + this.cors = cors; + } + + public static class Cors { + private List allowedOrigins = new ArrayList<>(List.of("http://localhost:5173", "http://127.0.0.1:5173")); + private List allowedMethods = new ArrayList<>(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS")); + private List allowedHeaders = new ArrayList<>(List.of("*")); + private boolean allowCredentials = true; + + public List getAllowedOrigins() { + return allowedOrigins; + } + + public void setAllowedOrigins(List allowedOrigins) { + this.allowedOrigins = allowedOrigins; + } + + public List getAllowedMethods() { + return allowedMethods; + } + + public void setAllowedMethods(List allowedMethods) { + this.allowedMethods = allowedMethods; + } + + public List getAllowedHeaders() { + return allowedHeaders; + } + + public void setAllowedHeaders(List allowedHeaders) { + this.allowedHeaders = allowedHeaders; + } + + public boolean isAllowCredentials() { + return allowCredentials; + } + + public void setAllowCredentials(boolean allowCredentials) { + this.allowCredentials = allowCredentials; + } + } +} diff --git a/src/main/java/com/noblelift/ota/config/SaTokenConfigure.java b/src/main/java/com/noblelift/ota/config/SaTokenConfigure.java new file mode 100644 index 0000000..bc103e8 --- /dev/null +++ b/src/main/java/com/noblelift/ota/config/SaTokenConfigure.java @@ -0,0 +1,18 @@ +package com.noblelift.ota.config; + +import cn.dev33.satoken.interceptor.SaInterceptor; +import cn.dev33.satoken.stp.StpUtil; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class SaTokenConfigure implements WebMvcConfigurer { + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(new SaInterceptor(handler -> StpUtil.checkLogin())) + .addPathPatterns("/api/releases/**", "/api/vehicles/**", "/api/assignments/**") + .excludePathPatterns("/api/auth/login", "/error"); + } +} diff --git a/src/main/java/com/noblelift/ota/config/SaTokenExceptionHandler.java b/src/main/java/com/noblelift/ota/config/SaTokenExceptionHandler.java new file mode 100644 index 0000000..a905bad --- /dev/null +++ b/src/main/java/com/noblelift/ota/config/SaTokenExceptionHandler.java @@ -0,0 +1,27 @@ +package com.noblelift.ota.config; + +import cn.dev33.satoken.exception.NotLoginException; +import cn.dev33.satoken.exception.NotRoleException; +import com.noblelift.ota.common.dto.ApiMessage; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import java.time.Instant; + +@RestControllerAdvice +public class SaTokenExceptionHandler { + + @ExceptionHandler(NotLoginException.class) + @ResponseStatus(HttpStatus.UNAUTHORIZED) + public ApiMessage handleNotLogin(NotLoginException ex) { + return new ApiMessage("UNAUTHORIZED", ex.getMessage(), Instant.now()); + } + + @ExceptionHandler(NotRoleException.class) + @ResponseStatus(HttpStatus.FORBIDDEN) + public ApiMessage handleNotRole(NotRoleException ex) { + return new ApiMessage("FORBIDDEN", ex.getMessage(), Instant.now()); + } +} diff --git a/src/main/java/com/noblelift/ota/domain/AgentStatus.java b/src/main/java/com/noblelift/ota/domain/AgentStatus.java new file mode 100644 index 0000000..10cfed1 --- /dev/null +++ b/src/main/java/com/noblelift/ota/domain/AgentStatus.java @@ -0,0 +1,15 @@ +package com.noblelift.ota.domain; + +public enum AgentStatus { + IDLE, + HAS_UPDATE, + WAIT_USER_CONFIRM, + BACKING_UP_DATABASE, + PULLING_IMAGE, + RESTARTING_SERVICE, + HEALTH_CHECKING, + SUCCESS, + FAILED, + ROLLBACKING, + ROLLED_BACK +} diff --git a/src/main/java/com/noblelift/ota/domain/TaskStatus.java b/src/main/java/com/noblelift/ota/domain/TaskStatus.java new file mode 100644 index 0000000..50d84f6 --- /dev/null +++ b/src/main/java/com/noblelift/ota/domain/TaskStatus.java @@ -0,0 +1,12 @@ +package com.noblelift.ota.domain; + +public enum TaskStatus { + PUBLISHED, + AVAILABLE, + WAITING_CONFIRM, + UPGRADING, + SUCCESS, + FAILED, + ROLLED_BACK, + SKIPPED +} diff --git a/src/main/java/com/noblelift/ota/domain/UpgradeMode.java b/src/main/java/com/noblelift/ota/domain/UpgradeMode.java new file mode 100644 index 0000000..324d66e --- /dev/null +++ b/src/main/java/com/noblelift/ota/domain/UpgradeMode.java @@ -0,0 +1,7 @@ +package com.noblelift.ota.domain; + +public enum UpgradeMode { + MANUAL_CONFIRM, + FORCE_CONFIRM, + AUTO +} diff --git a/src/main/java/com/noblelift/ota/module/agent/controller/AgentController.java b/src/main/java/com/noblelift/ota/module/agent/controller/AgentController.java new file mode 100644 index 0000000..1299927 --- /dev/null +++ b/src/main/java/com/noblelift/ota/module/agent/controller/AgentController.java @@ -0,0 +1,65 @@ +package com.noblelift.ota.module.agent.controller; + +import com.noblelift.ota.common.annotation.Log; +import com.noblelift.ota.common.dto.ApiMessage; +import com.noblelift.ota.config.AgentTokenAuthenticator; +import com.noblelift.ota.module.agent.dto.HeartbeatResponse; +import com.noblelift.ota.module.agent.dto.UpdateCheckResponse; +import com.noblelift.ota.module.agent.param.HeartbeatParam; +import com.noblelift.ota.module.agent.param.ReportParam; +import com.noblelift.ota.module.agent.param.UpdateCheckParam; +import com.noblelift.ota.module.agent.service.AgentService; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +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.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/agent") +@RequiredArgsConstructor +public class AgentController { + + private final AgentService agentService; + private final AgentTokenAuthenticator agentTokenAuthenticator; + + @Log("Agent心跳") + @PostMapping("/heartbeat") + public HeartbeatResponse heartbeat(HttpServletRequest request, @Valid @RequestBody HeartbeatParam param) { + agentTokenAuthenticator.verify(request); + return agentService.heartbeat(param); + } + + @Log("Agent检查更新") + @PostMapping("/update-check") + public UpdateCheckResponse updateCheck(HttpServletRequest request, @Valid @RequestBody UpdateCheckParam param) { + agentTokenAuthenticator.verify(request); + return agentService.updateCheck(param); + } + + @Log("Agent上报结果") + @PostMapping("/report") + public ApiMessage report(HttpServletRequest request, @Valid @RequestBody ReportParam param) { + agentTokenAuthenticator.verify(request); + return agentService.report(param); + } + + @Log("Agent确认升级") + @PostMapping("/confirm") + public ApiMessage confirmVehicleUpgrade(HttpServletRequest request, @Valid @RequestBody UpdateCheckParam param) { + agentTokenAuthenticator.verify(request); + return agentService.confirmUpgrade(param); + } + + @Log("Agent稍后升级") + @PostMapping("/postpone") + @ResponseStatus(HttpStatus.ACCEPTED) + public ApiMessage postponeVehicleUpgrade(HttpServletRequest request, @Valid @RequestBody UpdateCheckParam param) { + agentTokenAuthenticator.verify(request); + return agentService.postponeUpgrade(param); + } +} diff --git a/src/main/java/com/noblelift/ota/module/agent/dto/HeartbeatResponse.java b/src/main/java/com/noblelift/ota/module/agent/dto/HeartbeatResponse.java new file mode 100644 index 0000000..3b29888 --- /dev/null +++ b/src/main/java/com/noblelift/ota/module/agent/dto/HeartbeatResponse.java @@ -0,0 +1,17 @@ +package com.noblelift.ota.module.agent.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.Instant; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class HeartbeatResponse { + + private boolean accepted; + private Instant serverTime; + private String message; +} diff --git a/src/main/java/com/noblelift/ota/module/agent/dto/UpdateCheckResponse.java b/src/main/java/com/noblelift/ota/module/agent/dto/UpdateCheckResponse.java new file mode 100644 index 0000000..782afdd --- /dev/null +++ b/src/main/java/com/noblelift/ota/module/agent/dto/UpdateCheckResponse.java @@ -0,0 +1,16 @@ +package com.noblelift.ota.module.agent.dto; + +import com.noblelift.ota.common.model.ReleaseManifest; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class UpdateCheckResponse { + + private boolean hasUpdate; + private ReleaseManifest manifest; + private String message; +} diff --git a/src/main/java/com/noblelift/ota/module/agent/param/HeartbeatParam.java b/src/main/java/com/noblelift/ota/module/agent/param/HeartbeatParam.java new file mode 100644 index 0000000..28c5d94 --- /dev/null +++ b/src/main/java/com/noblelift/ota/module/agent/param/HeartbeatParam.java @@ -0,0 +1,40 @@ +package com.noblelift.ota.module.agent.param; + +import com.fasterxml.jackson.annotation.JsonAlias; +import com.noblelift.ota.domain.AgentStatus; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +import java.util.Map; + +@Data +public class HeartbeatParam { + + @NotBlank + @JsonAlias("vehicle_id") + private String vehicleId; + + @JsonAlias("vin") + private String vin; + + @NotBlank + @JsonAlias("current_release") + private String currentRelease; + + @JsonAlias({"agent_status", "status"}) + private AgentStatus agentStatus; + + @JsonAlias({"target_release", "release_version"}) + private String targetRelease; + + @JsonAlias("last_result") + private String lastResult; + + private Map images; + + @JsonAlias("backup_file") + private String backupFile; + + @JsonAlias("ip_address") + private String ipAddress; +} diff --git a/src/main/java/com/noblelift/ota/module/agent/param/ReportParam.java b/src/main/java/com/noblelift/ota/module/agent/param/ReportParam.java new file mode 100644 index 0000000..ef5d97b --- /dev/null +++ b/src/main/java/com/noblelift/ota/module/agent/param/ReportParam.java @@ -0,0 +1,38 @@ +package com.noblelift.ota.module.agent.param; + +import com.fasterxml.jackson.annotation.JsonAlias; +import com.noblelift.ota.domain.AgentStatus; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +import java.util.Map; + +@Data +public class ReportParam { + + @NotBlank + @JsonAlias("vehicle_id") + private String vehicleId; + + @JsonAlias("vin") + private String vin; + + @JsonAlias("current_release") + private String currentRelease; + + @JsonAlias({"release_version", "target_release"}) + private String releaseVersion; + + @JsonAlias({"agent_status", "status"}) + private AgentStatus agentStatus; + + private boolean success; + + @JsonAlias({"message", "detail"}) + private String message; + + private Map images; + + @JsonAlias("backup_file") + private String backupFile; +} diff --git a/src/main/java/com/noblelift/ota/module/agent/param/UpdateCheckParam.java b/src/main/java/com/noblelift/ota/module/agent/param/UpdateCheckParam.java new file mode 100644 index 0000000..1f8f161 --- /dev/null +++ b/src/main/java/com/noblelift/ota/module/agent/param/UpdateCheckParam.java @@ -0,0 +1,20 @@ +package com.noblelift.ota.module.agent.param; + +import com.fasterxml.jackson.annotation.JsonAlias; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +@Data +public class UpdateCheckParam { + + @NotBlank + @JsonAlias("vehicle_id") + private String vehicleId; + + @JsonAlias("vin") + private String vin; + + @NotBlank + @JsonAlias("current_release") + private String currentRelease; +} diff --git a/src/main/java/com/noblelift/ota/module/agent/service/AgentService.java b/src/main/java/com/noblelift/ota/module/agent/service/AgentService.java new file mode 100644 index 0000000..1848464 --- /dev/null +++ b/src/main/java/com/noblelift/ota/module/agent/service/AgentService.java @@ -0,0 +1,21 @@ +package com.noblelift.ota.module.agent.service; + +import com.noblelift.ota.common.dto.ApiMessage; +import com.noblelift.ota.module.agent.dto.HeartbeatResponse; +import com.noblelift.ota.module.agent.dto.UpdateCheckResponse; +import com.noblelift.ota.module.agent.param.HeartbeatParam; +import com.noblelift.ota.module.agent.param.ReportParam; +import com.noblelift.ota.module.agent.param.UpdateCheckParam; + +public interface AgentService { + + HeartbeatResponse heartbeat(HeartbeatParam param); + + UpdateCheckResponse updateCheck(UpdateCheckParam param); + + ApiMessage report(ReportParam param); + + ApiMessage confirmUpgrade(UpdateCheckParam param); + + ApiMessage postponeUpgrade(UpdateCheckParam param); +} diff --git a/src/main/java/com/noblelift/ota/module/agent/service/impl/AgentServiceImpl.java b/src/main/java/com/noblelift/ota/module/agent/service/impl/AgentServiceImpl.java new file mode 100644 index 0000000..70896c8 --- /dev/null +++ b/src/main/java/com/noblelift/ota/module/agent/service/impl/AgentServiceImpl.java @@ -0,0 +1,177 @@ +package com.noblelift.ota.module.agent.service.impl; + +import com.noblelift.ota.common.dto.ApiMessage; +import com.noblelift.ota.common.model.ReleaseManifest; +import com.noblelift.ota.common.model.VehicleAssignment; +import com.noblelift.ota.common.model.VehicleInfo; +import com.noblelift.ota.common.util.ReleaseVersionComparator; +import com.noblelift.ota.domain.AgentStatus; +import com.noblelift.ota.domain.TaskStatus; +import com.noblelift.ota.module.agent.dto.HeartbeatResponse; +import com.noblelift.ota.module.agent.dto.UpdateCheckResponse; +import com.noblelift.ota.module.agent.param.HeartbeatParam; +import com.noblelift.ota.module.agent.param.ReportParam; +import com.noblelift.ota.module.agent.param.UpdateCheckParam; +import com.noblelift.ota.module.agent.service.AgentService; +import com.noblelift.ota.module.assignment.service.AssignmentService; +import com.noblelift.ota.module.release.service.ReleaseService; +import com.noblelift.ota.module.vehicle.service.VehicleService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.time.Instant; + +@Service +@RequiredArgsConstructor +public class AgentServiceImpl implements AgentService { + + private static final int MAX_LAST_MESSAGE_LENGTH = 500; + + private final VehicleService vehicleService; + private final AssignmentService assignmentService; + private final ReleaseService releaseService; + + @Override + public HeartbeatResponse heartbeat(HeartbeatParam param) { + AgentStatus agentStatus = resolveAgentStatus(param.getAgentStatus()); + vehicleService.saveHeartbeat( + param.getVehicleId(), + param.getVin(), + param.getCurrentRelease(), + agentStatus, + param.getTargetRelease(), + param.getLastResult(), + param.getImages(), + param.getBackupFile() + ); + return new HeartbeatResponse(true, Instant.now(), "heartbeat accepted"); + } + + @Override + public UpdateCheckResponse updateCheck(UpdateCheckParam param) { + VehicleAssignment assignment; + try { + assignment = assignmentService.getAssignment(param.getVehicleId()); + } catch (IllegalArgumentException ex) { + return new UpdateCheckResponse(false, null, "No release assigned"); + } + + ReleaseManifest manifest = releaseService.getRelease(assignment.getReleaseVersion()); + int versionCompare = ReleaseVersionComparator.compare(manifest.getReleaseVersion(), param.getCurrentRelease()); + if (versionCompare == 0) { + return new UpdateCheckResponse(false, null, "Already on latest assigned release"); + } + if (versionCompare < 0) { + return new UpdateCheckResponse(false, null, "Assigned release is lower than current release"); + } + + if (assignment.getTaskStatus() == TaskStatus.AVAILABLE || assignment.getTaskStatus() == TaskStatus.SKIPPED) { + assignment.setTaskStatus(TaskStatus.WAITING_CONFIRM); + assignment.setPromptedAt(assignment.getPromptedAt() == null ? Instant.now() : assignment.getPromptedAt()); + assignment.setLastMessage("Update is visible to vehicle"); + assignmentService.saveAssignment(assignment); + } + return new UpdateCheckResponse(true, manifest, "Update available"); + } + + @Override + public ApiMessage report(ReportParam param) { + VehicleAssignment assignment = assignmentService.getAssignment(param.getVehicleId()); + AgentStatus agentStatus = resolveAgentStatus(param.getAgentStatus()); + boolean success = resolveSuccess(param, agentStatus); + TaskStatus status = mapTaskStatus(success, agentStatus); + Instant now = Instant.now(); + String releaseVersion = resolveReleaseVersion(param, assignment); + assignment.setReleaseVersion(releaseVersion); + assignment.setTaskStatus(status); + if (assignment.getConfirmedAt() == null && status == TaskStatus.UPGRADING) { + assignment.setConfirmedAt(now); + } + if (assignment.getStartedAt() == null && status == TaskStatus.UPGRADING) { + assignment.setStartedAt(now); + } + if (status == TaskStatus.SUCCESS || status == TaskStatus.FAILED || status == TaskStatus.ROLLED_BACK) { + assignment.setFinishedAt(now); + } + assignment.setLastMessage(truncateLastMessage(param.getMessage())); + assignmentService.saveAssignment(assignment); + + vehicleService.saveHeartbeat( + param.getVehicleId(), + param.getVin(), + resolveCurrentRelease(param, success, releaseVersion), + agentStatus, + releaseVersion, + truncateLastMessage(param.getMessage()), + param.getImages(), + param.getBackupFile() + ); + return new ApiMessage("OK", "report accepted", Instant.now()); + } + + @Override + public ApiMessage confirmUpgrade(UpdateCheckParam param) { + assignmentService.markConfirmed(param.getVehicleId()); + return new ApiMessage("OK", "vehicle upgrade confirmed", Instant.now()); + } + + @Override + public ApiMessage postponeUpgrade(UpdateCheckParam param) { + assignmentService.markPostponed(param.getVehicleId(), "vehicle postponed upgrade"); + return new ApiMessage("OK", "vehicle upgrade postponed", Instant.now()); + } + + private TaskStatus mapTaskStatus(boolean success, AgentStatus agentStatus) { + if (success && agentStatus == AgentStatus.SUCCESS) { + return TaskStatus.SUCCESS; + } + if (agentStatus == AgentStatus.ROLLBACKING || agentStatus == AgentStatus.ROLLED_BACK) { + return TaskStatus.ROLLED_BACK; + } + if (agentStatus == AgentStatus.BACKING_UP_DATABASE + || agentStatus == AgentStatus.PULLING_IMAGE + || agentStatus == AgentStatus.RESTARTING_SERVICE + || agentStatus == AgentStatus.HEALTH_CHECKING) { + return TaskStatus.UPGRADING; + } + return success ? TaskStatus.SUCCESS : TaskStatus.FAILED; + } + + private AgentStatus resolveAgentStatus(AgentStatus agentStatus) { + return agentStatus == null ? AgentStatus.IDLE : agentStatus; + } + + private boolean resolveSuccess(ReportParam param, AgentStatus agentStatus) { + if (agentStatus == AgentStatus.SUCCESS) { + return true; + } + if (agentStatus == AgentStatus.FAILED || agentStatus == AgentStatus.ROLLBACKING || agentStatus == AgentStatus.ROLLED_BACK) { + return false; + } + return param.isSuccess(); + } + + private String resolveReleaseVersion(ReportParam param, VehicleAssignment assignment) { + if (param.getReleaseVersion() != null && !param.getReleaseVersion().isBlank()) { + return param.getReleaseVersion(); + } + if (param.getCurrentRelease() != null && !param.getCurrentRelease().isBlank()) { + return param.getCurrentRelease(); + } + return assignment.getReleaseVersion(); + } + + private String resolveCurrentRelease(ReportParam param, boolean success, String releaseVersion) { + if (param.getCurrentRelease() != null && !param.getCurrentRelease().isBlank()) { + return param.getCurrentRelease(); + } + return success ? releaseVersion : null; + } + + private String truncateLastMessage(String message) { + if (message == null || message.length() <= MAX_LAST_MESSAGE_LENGTH) { + return message; + } + return message.substring(0, MAX_LAST_MESSAGE_LENGTH - 3) + "..."; + } +} diff --git a/src/main/java/com/noblelift/ota/module/assignment/controller/AssignmentController.java b/src/main/java/com/noblelift/ota/module/assignment/controller/AssignmentController.java new file mode 100644 index 0000000..0e5f1ea --- /dev/null +++ b/src/main/java/com/noblelift/ota/module/assignment/controller/AssignmentController.java @@ -0,0 +1,73 @@ +package com.noblelift.ota.module.assignment.controller; + +import cn.dev33.satoken.annotation.SaCheckRole; +import com.noblelift.ota.common.annotation.Log; +import com.noblelift.ota.module.assignment.dto.AssignmentView; +import com.noblelift.ota.module.assignment.param.AssignReleaseParam; +import com.noblelift.ota.module.assignment.service.AssignmentService; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +@RequestMapping("/api/assignments") +@RequiredArgsConstructor +public class AssignmentController { + + private final AssignmentService assignmentService; + + @SaCheckRole(value = {"SUPER_ADMIN", "ADMIN"}, mode = cn.dev33.satoken.annotation.SaMode.OR) + @Log("分配版本") + @PostMapping + public List assignRelease(@Valid @RequestBody AssignReleaseParam param) { + return assignmentService.assignRelease(param.getReleaseVersion(), param.getVehicleIds()).stream() + .map(this::toView) + .toList(); + } + + @SaCheckRole(value = {"SUPER_ADMIN", "ADMIN"}, mode = cn.dev33.satoken.annotation.SaMode.OR) + @Log("修改任务") + @PutMapping("/{vehicleId}") + public AssignmentView updateAssignment(@PathVariable String vehicleId, @Valid @RequestBody AssignReleaseParam param) { + return toView(assignmentService.updateAssignment(vehicleId, param)); + } + + @SaCheckRole(value = {"SUPER_ADMIN", "ADMIN"}, mode = cn.dev33.satoken.annotation.SaMode.OR) + @Log("删除任务") + @DeleteMapping("/{vehicleId}") + public void deleteAssignment(@PathVariable String vehicleId) { + assignmentService.deleteAssignment(vehicleId); + } + + @SaCheckRole(value = {"SUPER_ADMIN", "ADMIN"}, mode = cn.dev33.satoken.annotation.SaMode.OR) + @Log("查询任务列表") + @GetMapping + public List listAssignments() { + return assignmentService.listAssignments().stream() + .map(this::toView) + .toList(); + } + + private AssignmentView toView(com.noblelift.ota.common.model.VehicleAssignment item) { + return new AssignmentView( + item.getVehicleId(), + item.getReleaseVersion(), + item.getTaskStatus(), + item.getPromptedAt(), + item.getConfirmedAt(), + item.getStartedAt(), + item.getFinishedAt(), + item.getPostponeCount(), + item.getLastMessage() + ); + } +} diff --git a/src/main/java/com/noblelift/ota/module/assignment/dto/AssignmentView.java b/src/main/java/com/noblelift/ota/module/assignment/dto/AssignmentView.java new file mode 100644 index 0000000..c287322 --- /dev/null +++ b/src/main/java/com/noblelift/ota/module/assignment/dto/AssignmentView.java @@ -0,0 +1,24 @@ +package com.noblelift.ota.module.assignment.dto; + +import com.noblelift.ota.domain.TaskStatus; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.Instant; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class AssignmentView { + + private String vehicleId; + private String releaseVersion; + private TaskStatus taskStatus; + private Instant promptedAt; + private Instant confirmedAt; + private Instant startedAt; + private Instant finishedAt; + private Integer postponeCount; + private String lastMessage; +} diff --git a/src/main/java/com/noblelift/ota/module/assignment/entity/VehicleAssignmentEntity.java b/src/main/java/com/noblelift/ota/module/assignment/entity/VehicleAssignmentEntity.java new file mode 100644 index 0000000..afdd035 --- /dev/null +++ b/src/main/java/com/noblelift/ota/module/assignment/entity/VehicleAssignmentEntity.java @@ -0,0 +1,25 @@ +package com.noblelift.ota.module.assignment.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.Instant; + +@Data +@TableName("ota_vehicle_assignment") +public class VehicleAssignmentEntity { + + @TableId(type = IdType.AUTO) + private Long id; + private String vehicleId; + private String releaseVersion; + private String taskStatus; + private Instant promptedAt; + private Instant confirmedAt; + private Instant startedAt; + private Instant finishedAt; + private Integer postponeCount; + private String lastMessage; +} diff --git a/src/main/java/com/noblelift/ota/module/assignment/mapper/VehicleAssignmentMapper.java b/src/main/java/com/noblelift/ota/module/assignment/mapper/VehicleAssignmentMapper.java new file mode 100644 index 0000000..73a2b7a --- /dev/null +++ b/src/main/java/com/noblelift/ota/module/assignment/mapper/VehicleAssignmentMapper.java @@ -0,0 +1,9 @@ +package com.noblelift.ota.module.assignment.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.noblelift.ota.module.assignment.entity.VehicleAssignmentEntity; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface VehicleAssignmentMapper extends BaseMapper { +} diff --git a/src/main/java/com/noblelift/ota/module/assignment/param/AssignReleaseParam.java b/src/main/java/com/noblelift/ota/module/assignment/param/AssignReleaseParam.java new file mode 100644 index 0000000..9b17043 --- /dev/null +++ b/src/main/java/com/noblelift/ota/module/assignment/param/AssignReleaseParam.java @@ -0,0 +1,15 @@ +package com.noblelift.ota.module.assignment.param; + +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +import java.util.List; + +@Data +public class AssignReleaseParam { + + @NotBlank + private String releaseVersion; + + private List vehicleIds; +} diff --git a/src/main/java/com/noblelift/ota/module/assignment/service/AssignmentService.java b/src/main/java/com/noblelift/ota/module/assignment/service/AssignmentService.java new file mode 100644 index 0000000..7ad2c97 --- /dev/null +++ b/src/main/java/com/noblelift/ota/module/assignment/service/AssignmentService.java @@ -0,0 +1,25 @@ +package com.noblelift.ota.module.assignment.service; + +import com.noblelift.ota.common.model.VehicleAssignment; +import com.noblelift.ota.module.assignment.param.AssignReleaseParam; + +import java.util.List; + +public interface AssignmentService { + + List listAssignments(); + + List assignRelease(String releaseVersion, List vehicleIds); + + VehicleAssignment updateAssignment(String vehicleId, AssignReleaseParam param); + + void deleteAssignment(String vehicleId); + + VehicleAssignment getAssignment(String vehicleId); + + VehicleAssignment markPostponed(String vehicleId, String reason); + + VehicleAssignment markConfirmed(String vehicleId); + + VehicleAssignment saveAssignment(VehicleAssignment assignment); +} diff --git a/src/main/java/com/noblelift/ota/module/assignment/service/impl/AssignmentServiceImpl.java b/src/main/java/com/noblelift/ota/module/assignment/service/impl/AssignmentServiceImpl.java new file mode 100644 index 0000000..0151dac --- /dev/null +++ b/src/main/java/com/noblelift/ota/module/assignment/service/impl/AssignmentServiceImpl.java @@ -0,0 +1,170 @@ +package com.noblelift.ota.module.assignment.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.noblelift.ota.common.model.VehicleAssignment; +import com.noblelift.ota.common.model.VehicleInfo; +import com.noblelift.ota.common.util.ReleaseVersionComparator; +import com.noblelift.ota.domain.TaskStatus; +import com.noblelift.ota.module.assignment.entity.VehicleAssignmentEntity; +import com.noblelift.ota.module.assignment.mapper.VehicleAssignmentMapper; +import com.noblelift.ota.module.assignment.param.AssignReleaseParam; +import com.noblelift.ota.module.assignment.service.AssignmentService; +import com.noblelift.ota.module.release.service.ReleaseService; +import com.noblelift.ota.module.vehicle.service.VehicleService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.time.Instant; +import java.util.List; + +@Service +@RequiredArgsConstructor +public class AssignmentServiceImpl implements AssignmentService { + + private final VehicleAssignmentMapper vehicleAssignmentMapper; + private final VehicleService vehicleService; + private final ReleaseService releaseService; + + @Override + public List listAssignments() { + return vehicleAssignmentMapper.selectList(new LambdaQueryWrapper() + .orderByAsc(VehicleAssignmentEntity::getVehicleId)) + .stream() + .map(this::toModel) + .toList(); + } + + @Override + public List assignRelease(String releaseVersion, List vehicleIds) { + releaseService.getRelease(releaseVersion); + return vehicleIds.stream() + .map(vehicleId -> saveAssignment(buildAssignment(releaseVersion, vehicleId))) + .toList(); + } + + @Override + public VehicleAssignment updateAssignment(String vehicleId, AssignReleaseParam param) { + VehicleAssignmentEntity existing = getEntity(vehicleId); + VehicleAssignment assignment = buildAssignment(param.getReleaseVersion(), vehicleId); + assignment.setTaskStatus(existing.getTaskStatus() == null ? TaskStatus.AVAILABLE : TaskStatus.valueOf(existing.getTaskStatus())); + assignment.setPromptedAt(existing.getPromptedAt()); + assignment.setConfirmedAt(existing.getConfirmedAt()); + assignment.setStartedAt(existing.getStartedAt()); + assignment.setFinishedAt(existing.getFinishedAt()); + assignment.setPostponeCount(existing.getPostponeCount() == null ? 0 : existing.getPostponeCount()); + if (existing.getLastMessage() != null && !existing.getLastMessage().isBlank()) { + assignment.setLastMessage(existing.getLastMessage()); + } + return saveAssignment(assignment, existing.getId()); + } + + @Override + public void deleteAssignment(String vehicleId) { + VehicleAssignmentEntity existing = getEntity(vehicleId); + vehicleAssignmentMapper.deleteById(existing.getId()); + } + + @Override + public VehicleAssignment getAssignment(String vehicleId) { + return toModel(getEntity(vehicleId)); + } + + @Override + public VehicleAssignment markPostponed(String vehicleId, String reason) { + VehicleAssignment assignment = getAssignment(vehicleId); + assignment.setTaskStatus(TaskStatus.SKIPPED); + assignment.setPromptedAt(assignment.getPromptedAt() == null ? Instant.now() : assignment.getPromptedAt()); + assignment.setPostponeCount((assignment.getPostponeCount() == null ? 0 : assignment.getPostponeCount()) + 1); + assignment.setLastMessage(reason); + return saveAssignment(assignment); + } + + @Override + public VehicleAssignment markConfirmed(String vehicleId) { + VehicleAssignment assignment = getAssignment(vehicleId); + Instant now = Instant.now(); + assignment.setTaskStatus(TaskStatus.UPGRADING); + assignment.setPromptedAt(assignment.getPromptedAt() == null ? now : assignment.getPromptedAt()); + assignment.setConfirmedAt(now); + assignment.setStartedAt(now); + assignment.setLastMessage("User confirmed upgrade"); + return saveAssignment(assignment); + } + + @Override + public VehicleAssignment saveAssignment(VehicleAssignment assignment) { + VehicleAssignmentEntity existing = vehicleAssignmentMapper.selectOne(new LambdaQueryWrapper() + .eq(VehicleAssignmentEntity::getVehicleId, assignment.getVehicleId()) + .last("limit 1")); + return saveAssignment(assignment, existing == null ? null : existing.getId()); + } + + private VehicleAssignment saveAssignment(VehicleAssignment assignment, Long existingId) { + VehicleAssignmentEntity entity = toEntity(assignment); + if (existingId == null) { + vehicleAssignmentMapper.insert(entity); + } else { + entity.setId(existingId); + vehicleAssignmentMapper.updateById(entity); + } + return assignment; + } + + private VehicleAssignment buildAssignment(String releaseVersion, String vehicleId) { + VehicleInfo vehicle = vehicleService.listVehicles().stream() + .filter(item -> item.getVehicleId().equals(vehicleId)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("Vehicle not found: " + vehicleId)); + + int versionCompare = ReleaseVersionComparator.compare(releaseVersion, vehicle.getCurrentRelease()); + if (versionCompare <= 0) { + throw new IllegalArgumentException("Assigned release must be higher than current release for vehicle: " + vehicleId); + } + + return VehicleAssignment.builder() + .vehicleId(vehicleId) + .releaseVersion(releaseVersion) + .taskStatus(TaskStatus.AVAILABLE) + .postponeCount(0) + .lastMessage("Release available for upgrade") + .build(); + } + + private VehicleAssignmentEntity getEntity(String vehicleId) { + VehicleAssignmentEntity entity = vehicleAssignmentMapper.selectOne(new LambdaQueryWrapper() + .eq(VehicleAssignmentEntity::getVehicleId, vehicleId) + .last("limit 1")); + if (entity == null) { + throw new IllegalArgumentException("Assignment not found for vehicle: " + vehicleId); + } + return entity; + } + + private VehicleAssignment toModel(VehicleAssignmentEntity entity) { + return VehicleAssignment.builder() + .vehicleId(entity.getVehicleId()) + .releaseVersion(entity.getReleaseVersion()) + .taskStatus(TaskStatus.valueOf(entity.getTaskStatus())) + .promptedAt(entity.getPromptedAt()) + .confirmedAt(entity.getConfirmedAt()) + .startedAt(entity.getStartedAt()) + .finishedAt(entity.getFinishedAt()) + .postponeCount(entity.getPostponeCount() == null ? 0 : entity.getPostponeCount()) + .lastMessage(entity.getLastMessage()) + .build(); + } + + private VehicleAssignmentEntity toEntity(VehicleAssignment assignment) { + VehicleAssignmentEntity entity = new VehicleAssignmentEntity(); + entity.setVehicleId(assignment.getVehicleId()); + entity.setReleaseVersion(assignment.getReleaseVersion()); + entity.setTaskStatus(assignment.getTaskStatus().name()); + entity.setPromptedAt(assignment.getPromptedAt()); + entity.setConfirmedAt(assignment.getConfirmedAt()); + entity.setStartedAt(assignment.getStartedAt()); + entity.setFinishedAt(assignment.getFinishedAt()); + entity.setPostponeCount(assignment.getPostponeCount()); + entity.setLastMessage(assignment.getLastMessage()); + return entity; + } +} diff --git a/src/main/java/com/noblelift/ota/module/auth/controller/AuthController.java b/src/main/java/com/noblelift/ota/module/auth/controller/AuthController.java new file mode 100644 index 0000000..f88efaa --- /dev/null +++ b/src/main/java/com/noblelift/ota/module/auth/controller/AuthController.java @@ -0,0 +1,53 @@ +package com.noblelift.ota.module.auth.controller; + +import cn.dev33.satoken.annotation.SaCheckLogin; +import cn.dev33.satoken.stp.StpUtil; +import com.noblelift.ota.common.annotation.Log; +import com.noblelift.ota.common.dto.ApiMessage; +import com.noblelift.ota.module.auth.dto.LoginResponse; +import com.noblelift.ota.module.auth.param.LoginParam; +import com.noblelift.ota.module.auth.service.AuthService; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +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; + +import java.time.Instant; +import java.util.HashMap; +import java.util.Map; + +@RestController +@RequestMapping("/api/auth") +@RequiredArgsConstructor +public class AuthController { + + private final AuthService authService; + + @Log("用户登录") + @PostMapping("/login") + public LoginResponse login(@Valid @RequestBody LoginParam param) { + return authService.login(param); + } + + @Log("用户退出") + @SaCheckLogin + @PostMapping("/logout") + public ApiMessage logout() { + authService.logout(); + return new ApiMessage("OK", "logout success", Instant.now()); + } + + @Log("查询当前用户") + @SaCheckLogin + @GetMapping("/me") + public Map currentUser() { + Map result = new HashMap<>(); + result.put("loginId", StpUtil.getLoginIdAsLong()); + result.put("roles", authService.getCurrentRoles()); + result.put("token", StpUtil.getTokenValue()); + return result; + } +} diff --git a/src/main/java/com/noblelift/ota/module/auth/dto/LoginResponse.java b/src/main/java/com/noblelift/ota/module/auth/dto/LoginResponse.java new file mode 100644 index 0000000..99c04ee --- /dev/null +++ b/src/main/java/com/noblelift/ota/module/auth/dto/LoginResponse.java @@ -0,0 +1,19 @@ +package com.noblelift.ota.module.auth.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class LoginResponse { + + private String token; + private Long userId; + private String username; + private String nickname; + private List roles; +} diff --git a/src/main/java/com/noblelift/ota/module/auth/entity/SysRoleEntity.java b/src/main/java/com/noblelift/ota/module/auth/entity/SysRoleEntity.java new file mode 100644 index 0000000..4c7c6f9 --- /dev/null +++ b/src/main/java/com/noblelift/ota/module/auth/entity/SysRoleEntity.java @@ -0,0 +1,21 @@ +package com.noblelift.ota.module.auth.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.Instant; + +@Data +@TableName("sys_role") +public class SysRoleEntity { + + @TableId(type = IdType.AUTO) + private Long id; + private String roleCode; + private String roleName; + private Integer status; + private Instant createdAt; + private Instant updatedAt; +} diff --git a/src/main/java/com/noblelift/ota/module/auth/entity/SysUserEntity.java b/src/main/java/com/noblelift/ota/module/auth/entity/SysUserEntity.java new file mode 100644 index 0000000..7e7c9a3 --- /dev/null +++ b/src/main/java/com/noblelift/ota/module/auth/entity/SysUserEntity.java @@ -0,0 +1,22 @@ +package com.noblelift.ota.module.auth.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.Instant; + +@Data +@TableName("sys_user") +public class SysUserEntity { + + @TableId(type = IdType.AUTO) + private Long id; + private String username; + private String password; + private String nickname; + private Integer status; + private Instant createdAt; + private Instant updatedAt; +} diff --git a/src/main/java/com/noblelift/ota/module/auth/entity/SysUserRoleEntity.java b/src/main/java/com/noblelift/ota/module/auth/entity/SysUserRoleEntity.java new file mode 100644 index 0000000..6f7f33f --- /dev/null +++ b/src/main/java/com/noblelift/ota/module/auth/entity/SysUserRoleEntity.java @@ -0,0 +1,19 @@ +package com.noblelift.ota.module.auth.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.Instant; + +@Data +@TableName("sys_user_role") +public class SysUserRoleEntity { + + @TableId(type = IdType.AUTO) + private Long id; + private Long userId; + private Long roleId; + private Instant createdAt; +} diff --git a/src/main/java/com/noblelift/ota/module/auth/mapper/SysRoleMapper.java b/src/main/java/com/noblelift/ota/module/auth/mapper/SysRoleMapper.java new file mode 100644 index 0000000..23f2400 --- /dev/null +++ b/src/main/java/com/noblelift/ota/module/auth/mapper/SysRoleMapper.java @@ -0,0 +1,9 @@ +package com.noblelift.ota.module.auth.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.noblelift.ota.module.auth.entity.SysRoleEntity; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface SysRoleMapper extends BaseMapper { +} diff --git a/src/main/java/com/noblelift/ota/module/auth/mapper/SysUserMapper.java b/src/main/java/com/noblelift/ota/module/auth/mapper/SysUserMapper.java new file mode 100644 index 0000000..2f79cd1 --- /dev/null +++ b/src/main/java/com/noblelift/ota/module/auth/mapper/SysUserMapper.java @@ -0,0 +1,9 @@ +package com.noblelift.ota.module.auth.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.noblelift.ota.module.auth.entity.SysUserEntity; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface SysUserMapper extends BaseMapper { +} diff --git a/src/main/java/com/noblelift/ota/module/auth/mapper/SysUserRoleMapper.java b/src/main/java/com/noblelift/ota/module/auth/mapper/SysUserRoleMapper.java new file mode 100644 index 0000000..b834ae6 --- /dev/null +++ b/src/main/java/com/noblelift/ota/module/auth/mapper/SysUserRoleMapper.java @@ -0,0 +1,9 @@ +package com.noblelift.ota.module.auth.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.noblelift.ota.module.auth.entity.SysUserRoleEntity; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface SysUserRoleMapper extends BaseMapper { +} diff --git a/src/main/java/com/noblelift/ota/module/auth/param/LoginParam.java b/src/main/java/com/noblelift/ota/module/auth/param/LoginParam.java new file mode 100644 index 0000000..729fcf5 --- /dev/null +++ b/src/main/java/com/noblelift/ota/module/auth/param/LoginParam.java @@ -0,0 +1,14 @@ +package com.noblelift.ota.module.auth.param; + +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +@Data +public class LoginParam { + + @NotBlank + private String username; + + @NotBlank + private String password; +} diff --git a/src/main/java/com/noblelift/ota/module/auth/service/AuthService.java b/src/main/java/com/noblelift/ota/module/auth/service/AuthService.java new file mode 100644 index 0000000..61f6525 --- /dev/null +++ b/src/main/java/com/noblelift/ota/module/auth/service/AuthService.java @@ -0,0 +1,15 @@ +package com.noblelift.ota.module.auth.service; + +import com.noblelift.ota.module.auth.dto.LoginResponse; +import com.noblelift.ota.module.auth.param.LoginParam; + +import java.util.List; + +public interface AuthService { + + LoginResponse login(LoginParam param); + + void logout(); + + List getCurrentRoles(); +} diff --git a/src/main/java/com/noblelift/ota/module/auth/service/impl/AuthServiceImpl.java b/src/main/java/com/noblelift/ota/module/auth/service/impl/AuthServiceImpl.java new file mode 100644 index 0000000..53ff3e6 --- /dev/null +++ b/src/main/java/com/noblelift/ota/module/auth/service/impl/AuthServiceImpl.java @@ -0,0 +1,76 @@ +package com.noblelift.ota.module.auth.service.impl; + +import cn.dev33.satoken.stp.StpUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.noblelift.ota.module.auth.dto.LoginResponse; +import com.noblelift.ota.module.auth.entity.SysRoleEntity; +import com.noblelift.ota.module.auth.entity.SysUserEntity; +import com.noblelift.ota.module.auth.entity.SysUserRoleEntity; +import com.noblelift.ota.module.auth.mapper.SysRoleMapper; +import com.noblelift.ota.module.auth.mapper.SysUserMapper; +import com.noblelift.ota.module.auth.mapper.SysUserRoleMapper; +import com.noblelift.ota.module.auth.param.LoginParam; +import com.noblelift.ota.module.auth.service.AuthService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.util.DigestUtils; + +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.List; + +@Service +@RequiredArgsConstructor +public class AuthServiceImpl implements AuthService { + + private final SysUserMapper sysUserMapper; + private final SysRoleMapper sysRoleMapper; + private final SysUserRoleMapper sysUserRoleMapper; + + @Override + public LoginResponse login(LoginParam param) { + SysUserEntity user = sysUserMapper.selectOne(new LambdaQueryWrapper() + .eq(SysUserEntity::getUsername, param.getUsername()) + .last("limit 1")); + if (user == null || user.getStatus() == null || user.getStatus() != 1) { + throw new IllegalArgumentException("用户不存在或已禁用"); + } + + String encodedPassword = DigestUtils.md5DigestAsHex(param.getPassword().getBytes(StandardCharsets.UTF_8)); + if (!encodedPassword.equals(user.getPassword())) { + throw new IllegalArgumentException("用户名或密码错误"); + } + + StpUtil.login(user.getId()); + List roles = getRolesByUserId(user.getId()); + return new LoginResponse(StpUtil.getTokenValue(), user.getId(), user.getUsername(), user.getNickname(), roles); + } + + @Override + public void logout() { + StpUtil.logout(); + } + + @Override + public List getCurrentRoles() { + Object loginId = StpUtil.getLoginIdDefaultNull(); + if (loginId == null) { + return Collections.emptyList(); + } + return getRolesByUserId(Long.valueOf(String.valueOf(loginId))); + } + + private List getRolesByUserId(Long userId) { + List roleIds = sysUserRoleMapper.selectList(new LambdaQueryWrapper() + .eq(SysUserRoleEntity::getUserId, userId)) + .stream() + .map(SysUserRoleEntity::getRoleId) + .toList(); + if (roleIds.isEmpty()) { + return Collections.emptyList(); + } + return sysRoleMapper.selectBatchIds(roleIds).stream() + .map(SysRoleEntity::getRoleCode) + .toList(); + } +} diff --git a/src/main/java/com/noblelift/ota/module/auth/service/impl/StpInterfaceImpl.java b/src/main/java/com/noblelift/ota/module/auth/service/impl/StpInterfaceImpl.java new file mode 100644 index 0000000..e040d34 --- /dev/null +++ b/src/main/java/com/noblelift/ota/module/auth/service/impl/StpInterfaceImpl.java @@ -0,0 +1,42 @@ +package com.noblelift.ota.module.auth.service.impl; + +import cn.dev33.satoken.stp.StpInterface; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.noblelift.ota.module.auth.entity.SysRoleEntity; +import com.noblelift.ota.module.auth.entity.SysUserRoleEntity; +import com.noblelift.ota.module.auth.mapper.SysRoleMapper; +import com.noblelift.ota.module.auth.mapper.SysUserRoleMapper; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import java.util.Collections; +import java.util.List; + +@Component +@RequiredArgsConstructor +public class StpInterfaceImpl implements StpInterface { + + private final SysUserRoleMapper sysUserRoleMapper; + private final SysRoleMapper sysRoleMapper; + + @Override + public List getPermissionList(Object loginId, String loginType) { + return Collections.emptyList(); + } + + @Override + public List getRoleList(Object loginId, String loginType) { + Long userId = Long.valueOf(String.valueOf(loginId)); + List roleIds = sysUserRoleMapper.selectList(new LambdaQueryWrapper() + .eq(SysUserRoleEntity::getUserId, userId)) + .stream() + .map(SysUserRoleEntity::getRoleId) + .toList(); + if (roleIds.isEmpty()) { + return Collections.emptyList(); + } + return sysRoleMapper.selectBatchIds(roleIds).stream() + .map(SysRoleEntity::getRoleCode) + .toList(); + } +} diff --git a/src/main/java/com/noblelift/ota/module/release/controller/ReleaseController.java b/src/main/java/com/noblelift/ota/module/release/controller/ReleaseController.java new file mode 100644 index 0000000..fd882d5 --- /dev/null +++ b/src/main/java/com/noblelift/ota/module/release/controller/ReleaseController.java @@ -0,0 +1,57 @@ +package com.noblelift.ota.module.release.controller; + +import cn.dev33.satoken.annotation.SaCheckRole; +import com.noblelift.ota.common.annotation.Log; +import com.noblelift.ota.module.release.dto.ReleaseView; +import com.noblelift.ota.module.release.param.CreateReleaseParam; +import com.noblelift.ota.module.release.service.ReleaseService; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +@RequestMapping("/api/releases") +@RequiredArgsConstructor +public class ReleaseController { + + private final ReleaseService releaseService; + + @SaCheckRole(value = {"SUPER_ADMIN", "ADMIN"}, mode = cn.dev33.satoken.annotation.SaMode.OR) + @Log("创建版本") + @PostMapping + public ReleaseView createRelease(@Valid @RequestBody CreateReleaseParam param) { + return new ReleaseView(releaseService.createRelease(param)); + } + + @SaCheckRole(value = {"SUPER_ADMIN", "ADMIN"}, mode = cn.dev33.satoken.annotation.SaMode.OR) + @Log("修改版本") + @PutMapping("/{releaseVersion}") + public ReleaseView updateRelease(@PathVariable String releaseVersion, @Valid @RequestBody CreateReleaseParam param) { + return new ReleaseView(releaseService.updateRelease(releaseVersion, param)); + } + + @SaCheckRole(value = {"SUPER_ADMIN", "ADMIN"}, mode = cn.dev33.satoken.annotation.SaMode.OR) + @Log("删除版本") + @DeleteMapping("/{releaseVersion}") + public void deleteRelease(@PathVariable String releaseVersion) { + releaseService.deleteRelease(releaseVersion); + } + + @SaCheckRole(value = {"SUPER_ADMIN", "ADMIN"}, mode = cn.dev33.satoken.annotation.SaMode.OR) + @Log("查询版本列表") + @GetMapping + public List listReleases() { + return releaseService.listReleases().stream() + .map(ReleaseView::new) + .toList(); + } +} diff --git a/src/main/java/com/noblelift/ota/module/release/dto/ReleaseView.java b/src/main/java/com/noblelift/ota/module/release/dto/ReleaseView.java new file mode 100644 index 0000000..458d3ca --- /dev/null +++ b/src/main/java/com/noblelift/ota/module/release/dto/ReleaseView.java @@ -0,0 +1,14 @@ +package com.noblelift.ota.module.release.dto; + +import com.noblelift.ota.common.model.ReleaseManifest; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ReleaseView { + + private ReleaseManifest manifest; +} diff --git a/src/main/java/com/noblelift/ota/module/release/entity/ReleaseEntity.java b/src/main/java/com/noblelift/ota/module/release/entity/ReleaseEntity.java new file mode 100644 index 0000000..66c2ff7 --- /dev/null +++ b/src/main/java/com/noblelift/ota/module/release/entity/ReleaseEntity.java @@ -0,0 +1,22 @@ +package com.noblelift.ota.module.release.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.Instant; + +@Data +@TableName("ota_release") +public class ReleaseEntity { + + @TableId(type = IdType.AUTO) + private Long id; + private String releaseVersion; + private String releaseNotes; + private String upgradeMode; + private Instant publishedAt; + private Boolean parkingRequired; + private String componentsJson; +} diff --git a/src/main/java/com/noblelift/ota/module/release/mapper/ReleaseMapper.java b/src/main/java/com/noblelift/ota/module/release/mapper/ReleaseMapper.java new file mode 100644 index 0000000..2af6f0a --- /dev/null +++ b/src/main/java/com/noblelift/ota/module/release/mapper/ReleaseMapper.java @@ -0,0 +1,9 @@ +package com.noblelift.ota.module.release.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.noblelift.ota.module.release.entity.ReleaseEntity; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface ReleaseMapper extends BaseMapper { +} diff --git a/src/main/java/com/noblelift/ota/module/release/param/CreateReleaseParam.java b/src/main/java/com/noblelift/ota/module/release/param/CreateReleaseParam.java new file mode 100644 index 0000000..b9c6382 --- /dev/null +++ b/src/main/java/com/noblelift/ota/module/release/param/CreateReleaseParam.java @@ -0,0 +1,25 @@ +package com.noblelift.ota.module.release.param; + +import com.noblelift.ota.domain.UpgradeMode; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; + +import java.util.Map; + +@Data +public class CreateReleaseParam { + + @NotBlank + private String releaseVersion; + + @NotBlank + private String releaseNotes; + + private UpgradeMode upgradeMode; + + private Boolean parkingRequired; + + @NotEmpty + private Map components; +} diff --git a/src/main/java/com/noblelift/ota/module/release/service/ReleaseService.java b/src/main/java/com/noblelift/ota/module/release/service/ReleaseService.java new file mode 100644 index 0000000..dc547c6 --- /dev/null +++ b/src/main/java/com/noblelift/ota/module/release/service/ReleaseService.java @@ -0,0 +1,19 @@ +package com.noblelift.ota.module.release.service; + +import com.noblelift.ota.common.model.ReleaseManifest; +import com.noblelift.ota.module.release.param.CreateReleaseParam; + +import java.util.List; + +public interface ReleaseService { + + ReleaseManifest createRelease(CreateReleaseParam param); + + ReleaseManifest updateRelease(String releaseVersion, CreateReleaseParam param); + + void deleteRelease(String releaseVersion); + + List listReleases(); + + ReleaseManifest getRelease(String releaseVersion); +} diff --git a/src/main/java/com/noblelift/ota/module/release/service/impl/ReleaseServiceImpl.java b/src/main/java/com/noblelift/ota/module/release/service/impl/ReleaseServiceImpl.java new file mode 100644 index 0000000..2d80a03 --- /dev/null +++ b/src/main/java/com/noblelift/ota/module/release/service/impl/ReleaseServiceImpl.java @@ -0,0 +1,158 @@ +package com.noblelift.ota.module.release.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.noblelift.ota.common.model.ReleaseManifest; +import com.noblelift.ota.domain.UpgradeMode; +import com.noblelift.ota.module.assignment.entity.VehicleAssignmentEntity; +import com.noblelift.ota.module.assignment.mapper.VehicleAssignmentMapper; +import com.noblelift.ota.module.release.entity.ReleaseEntity; +import com.noblelift.ota.module.release.mapper.ReleaseMapper; +import com.noblelift.ota.module.release.param.CreateReleaseParam; +import com.noblelift.ota.module.release.service.ReleaseService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.time.Instant; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +@Service +@RequiredArgsConstructor +public class ReleaseServiceImpl implements ReleaseService { + + private final ReleaseMapper releaseMapper; + private final VehicleAssignmentMapper vehicleAssignmentMapper; + private final ObjectMapper objectMapper; + + @Override + public ReleaseManifest createRelease(CreateReleaseParam param) { + ReleaseManifest manifest = ReleaseManifest.builder() + .releaseVersion(param.getReleaseVersion()) + .releaseNotes(param.getReleaseNotes()) + .upgradeMode(param.getUpgradeMode() == null ? UpgradeMode.MANUAL_CONFIRM : param.getUpgradeMode()) + .publishedAt(Instant.now()) + .parkingRequired(Boolean.TRUE.equals(param.getParkingRequired())) + .components(param.getComponents()) + .build(); + + ReleaseEntity existing = releaseMapper.selectOne(new LambdaQueryWrapper() + .eq(ReleaseEntity::getReleaseVersion, manifest.getReleaseVersion()) + .last("limit 1")); + + ReleaseEntity entity = toEntity(manifest); + if (existing == null) { + releaseMapper.insert(entity); + } else { + entity.setId(existing.getId()); + releaseMapper.updateById(entity); + } + return manifest; + } + + @Override + public ReleaseManifest updateRelease(String releaseVersion, CreateReleaseParam param) { + ReleaseEntity existing = getEntity(releaseVersion); + ReleaseManifest manifest = ReleaseManifest.builder() + .releaseVersion(param.getReleaseVersion()) + .releaseNotes(param.getReleaseNotes()) + .upgradeMode(param.getUpgradeMode() == null ? UpgradeMode.MANUAL_CONFIRM : param.getUpgradeMode()) + .publishedAt(existing.getPublishedAt()) + .parkingRequired(Boolean.TRUE.equals(param.getParkingRequired())) + .components(param.getComponents()) + .build(); + + ReleaseEntity duplicate = releaseMapper.selectOne(new LambdaQueryWrapper() + .eq(ReleaseEntity::getReleaseVersion, manifest.getReleaseVersion()) + .last("limit 1")); + if (duplicate != null && !duplicate.getId().equals(existing.getId())) { + throw new IllegalArgumentException("版本号已存在:" + manifest.getReleaseVersion()); + } + + ReleaseEntity entity = toEntity(manifest); + entity.setId(existing.getId()); + releaseMapper.updateById(entity); + return manifest; + } + + @Override + public void deleteRelease(String releaseVersion) { + ReleaseEntity entity = getEntity(releaseVersion); + VehicleAssignmentEntity assignment = vehicleAssignmentMapper.selectOne(new LambdaQueryWrapper() + .eq(VehicleAssignmentEntity::getReleaseVersion, releaseVersion) + .last("limit 1")); + if (assignment != null) { + throw new IllegalArgumentException("该版本已被任务引用,无法删除:" + releaseVersion); + } + releaseMapper.deleteById(entity.getId()); + } + + @Override + public List listReleases() { + return releaseMapper.selectList(new LambdaQueryWrapper() + .orderByDesc(ReleaseEntity::getPublishedAt)) + .stream() + .map(this::toModel) + .toList(); + } + + @Override + public ReleaseManifest getRelease(String releaseVersion) { + return toModel(getEntity(releaseVersion)); + } + + private ReleaseEntity getEntity(String releaseVersion) { + ReleaseEntity entity = releaseMapper.selectOne(new LambdaQueryWrapper() + .eq(ReleaseEntity::getReleaseVersion, releaseVersion) + .last("limit 1")); + if (entity == null) { + throw new IllegalArgumentException("Release not found: " + releaseVersion); + } + return entity; + } + + private ReleaseManifest toModel(ReleaseEntity entity) { + return ReleaseManifest.builder() + .releaseVersion(entity.getReleaseVersion()) + .releaseNotes(entity.getReleaseNotes()) + .upgradeMode(UpgradeMode.valueOf(entity.getUpgradeMode())) + .publishedAt(entity.getPublishedAt()) + .parkingRequired(Boolean.TRUE.equals(entity.getParkingRequired())) + .components(readComponents(entity.getComponentsJson())) + .build(); + } + + private ReleaseEntity toEntity(ReleaseManifest manifest) { + ReleaseEntity entity = new ReleaseEntity(); + entity.setReleaseVersion(manifest.getReleaseVersion()); + entity.setReleaseNotes(manifest.getReleaseNotes()); + entity.setUpgradeMode(manifest.getUpgradeMode().name()); + entity.setPublishedAt(manifest.getPublishedAt()); + entity.setParkingRequired(manifest.getParkingRequired()); + entity.setComponentsJson(writeComponents(manifest.getComponents())); + return entity; + } + + private Map readComponents(String json) { + if (json == null || json.isBlank()) { + return Collections.emptyMap(); + } + try { + return objectMapper.readValue(json, new TypeReference<>() { + }); + } catch (JsonProcessingException e) { + throw new IllegalStateException("Failed to parse componentsJson", e); + } + } + + private String writeComponents(Map components) { + try { + return objectMapper.writeValueAsString(components == null ? Collections.emptyMap() : components); + } catch (JsonProcessingException e) { + throw new IllegalStateException("Failed to serialize componentsJson", e); + } + } +} diff --git a/src/main/java/com/noblelift/ota/module/vehicle/controller/VehicleController.java b/src/main/java/com/noblelift/ota/module/vehicle/controller/VehicleController.java new file mode 100644 index 0000000..9788ad3 --- /dev/null +++ b/src/main/java/com/noblelift/ota/module/vehicle/controller/VehicleController.java @@ -0,0 +1,75 @@ +package com.noblelift.ota.module.vehicle.controller; + +import cn.dev33.satoken.annotation.SaCheckRole; +import com.noblelift.ota.common.annotation.Log; +import com.noblelift.ota.module.vehicle.dto.VehicleView; +import com.noblelift.ota.module.vehicle.param.RegisterVehicleParam; +import com.noblelift.ota.module.vehicle.service.VehicleService; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +@RequestMapping("/api/vehicles") +@RequiredArgsConstructor +public class VehicleController { + + private final VehicleService vehicleService; + + @SaCheckRole(value = {"SUPER_ADMIN", "ADMIN"}, mode = cn.dev33.satoken.annotation.SaMode.OR) + @Log("注册车辆") + @PostMapping + public VehicleView registerVehicle(@Valid @RequestBody RegisterVehicleParam param) { + return toView(vehicleService.registerVehicle(param)); + } + + @SaCheckRole(value = {"SUPER_ADMIN", "ADMIN"}, mode = cn.dev33.satoken.annotation.SaMode.OR) + @Log("修改车辆") + @PutMapping("/{vehicleId}") + public VehicleView updateVehicle(@PathVariable String vehicleId, @Valid @RequestBody RegisterVehicleParam param) { + return toView(vehicleService.updateVehicle(vehicleId, param)); + } + + @SaCheckRole(value = {"SUPER_ADMIN", "ADMIN"}, mode = cn.dev33.satoken.annotation.SaMode.OR) + @Log("删除车辆") + @DeleteMapping("/{vehicleId}") + public void deleteVehicle(@PathVariable String vehicleId) { + vehicleService.deleteVehicle(vehicleId); + } + + @SaCheckRole(value = {"SUPER_ADMIN", "ADMIN"}, mode = cn.dev33.satoken.annotation.SaMode.OR) + @Log("查询车辆列表") + @GetMapping + public List listVehicles() { + return vehicleService.listVehicles().stream() + .map(this::toView) + .toList(); + } + + private VehicleView toView(com.noblelift.ota.common.model.VehicleInfo vehicle) { + return new VehicleView( + vehicle.getVehicleId(), + vehicle.getVin(), + vehicle.getCurrentRelease(), + vehicle.getCurrentBackendVersion(), + vehicle.getCurrentFrontendVersion(), + vehicle.getCurrentRosVersion(), + vehicle.getLastSeenAt(), + vehicle.getAgentStatus(), + vehicle.getTargetRelease(), + vehicle.getLastResult(), + vehicle.getImages(), + vehicle.getBackupFile(), + vehicle.getOnline() + ); + } +} diff --git a/src/main/java/com/noblelift/ota/module/vehicle/dto/VehicleView.java b/src/main/java/com/noblelift/ota/module/vehicle/dto/VehicleView.java new file mode 100644 index 0000000..68ed698 --- /dev/null +++ b/src/main/java/com/noblelift/ota/module/vehicle/dto/VehicleView.java @@ -0,0 +1,29 @@ +package com.noblelift.ota.module.vehicle.dto; + +import com.noblelift.ota.domain.AgentStatus; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.Instant; +import java.util.Map; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class VehicleView { + + private String vehicleId; + private String vin; + private String currentRelease; + private String currentBackendVersion; + private String currentFrontendVersion; + private String currentRosVersion; + private Instant lastSeenAt; + private AgentStatus agentStatus; + private String targetRelease; + private String lastResult; + private Map images; + private String backupFile; + private Boolean online; +} diff --git a/src/main/java/com/noblelift/ota/module/vehicle/entity/VehicleEntity.java b/src/main/java/com/noblelift/ota/module/vehicle/entity/VehicleEntity.java new file mode 100644 index 0000000..bc1cce0 --- /dev/null +++ b/src/main/java/com/noblelift/ota/module/vehicle/entity/VehicleEntity.java @@ -0,0 +1,28 @@ +package com.noblelift.ota.module.vehicle.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.Instant; + +@Data +@TableName("ota_vehicle") +public class VehicleEntity { + + @TableId(type = IdType.AUTO) + private Long id; + private String vehicleId; + private String vin; + private String currentRelease; + private String currentBackendVersion; + private String currentFrontendVersion; + private String currentRosVersion; + private Instant lastSeenAt; + private String agentStatus; + private String targetRelease; + private String lastResult; + private String imagesJson; + private String backupFile; +} diff --git a/src/main/java/com/noblelift/ota/module/vehicle/mapper/VehicleMapper.java b/src/main/java/com/noblelift/ota/module/vehicle/mapper/VehicleMapper.java new file mode 100644 index 0000000..ca984bc --- /dev/null +++ b/src/main/java/com/noblelift/ota/module/vehicle/mapper/VehicleMapper.java @@ -0,0 +1,9 @@ +package com.noblelift.ota.module.vehicle.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.noblelift.ota.module.vehicle.entity.VehicleEntity; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface VehicleMapper extends BaseMapper { +} diff --git a/src/main/java/com/noblelift/ota/module/vehicle/param/RegisterVehicleParam.java b/src/main/java/com/noblelift/ota/module/vehicle/param/RegisterVehicleParam.java new file mode 100644 index 0000000..6988ebd --- /dev/null +++ b/src/main/java/com/noblelift/ota/module/vehicle/param/RegisterVehicleParam.java @@ -0,0 +1,21 @@ +package com.noblelift.ota.module.vehicle.param; + +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +@Data +public class RegisterVehicleParam { + + @NotBlank + private String vehicleId; + + @NotBlank + private String vin; + + @NotBlank + private String currentRelease; + + private String currentBackendVersion; + private String currentFrontendVersion; + private String currentRosVersion; +} diff --git a/src/main/java/com/noblelift/ota/module/vehicle/service/VehicleService.java b/src/main/java/com/noblelift/ota/module/vehicle/service/VehicleService.java new file mode 100644 index 0000000..358ebe3 --- /dev/null +++ b/src/main/java/com/noblelift/ota/module/vehicle/service/VehicleService.java @@ -0,0 +1,28 @@ +package com.noblelift.ota.module.vehicle.service; + +import com.noblelift.ota.common.model.VehicleInfo; +import com.noblelift.ota.domain.AgentStatus; +import com.noblelift.ota.module.vehicle.param.RegisterVehicleParam; + +import java.util.List; +import java.util.Map; + +public interface VehicleService { + + VehicleInfo registerVehicle(RegisterVehicleParam param); + + VehicleInfo updateVehicle(String vehicleId, RegisterVehicleParam param); + + void deleteVehicle(String vehicleId); + + List listVehicles(); + + VehicleInfo saveHeartbeat(String vehicleId, + String vin, + String currentRelease, + AgentStatus agentStatus, + String targetRelease, + String lastResult, + Map images, + String backupFile); +} diff --git a/src/main/java/com/noblelift/ota/module/vehicle/service/impl/VehicleServiceImpl.java b/src/main/java/com/noblelift/ota/module/vehicle/service/impl/VehicleServiceImpl.java new file mode 100644 index 0000000..fff1e4b --- /dev/null +++ b/src/main/java/com/noblelift/ota/module/vehicle/service/impl/VehicleServiceImpl.java @@ -0,0 +1,235 @@ +package com.noblelift.ota.module.vehicle.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.noblelift.ota.common.model.VehicleInfo; +import com.noblelift.ota.config.OtaProperties; +import com.noblelift.ota.domain.AgentStatus; +import com.noblelift.ota.module.assignment.entity.VehicleAssignmentEntity; +import com.noblelift.ota.module.assignment.mapper.VehicleAssignmentMapper; +import com.noblelift.ota.module.vehicle.entity.VehicleEntity; +import com.noblelift.ota.module.vehicle.mapper.VehicleMapper; +import com.noblelift.ota.module.vehicle.param.RegisterVehicleParam; +import com.noblelift.ota.module.vehicle.service.VehicleService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.time.Instant; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +@Service +@RequiredArgsConstructor +public class VehicleServiceImpl implements VehicleService { + + private static final TypeReference> STRING_MAP_TYPE = new TypeReference<>() { + }; + + private final VehicleMapper vehicleMapper; + private final VehicleAssignmentMapper vehicleAssignmentMapper; + private final OtaProperties otaProperties; + private final ObjectMapper objectMapper; + + @Override + public VehicleInfo registerVehicle(RegisterVehicleParam param) { + VehicleEntity current = vehicleMapper.selectOne(new LambdaQueryWrapper() + .eq(VehicleEntity::getVehicleId, param.getVehicleId()) + .last("limit 1")); + + VehicleInfo vehicleInfo = VehicleInfo.builder() + .vehicleId(param.getVehicleId()) + .vin(param.getVin()) + .currentRelease(param.getCurrentRelease()) + .currentBackendVersion(param.getCurrentBackendVersion()) + .currentFrontendVersion(param.getCurrentFrontendVersion()) + .currentRosVersion(param.getCurrentRosVersion()) + .lastSeenAt(current == null ? null : current.getLastSeenAt()) + .agentStatus(current == null || current.getAgentStatus() == null ? AgentStatus.IDLE : AgentStatus.valueOf(current.getAgentStatus())) + .targetRelease(current == null ? null : current.getTargetRelease()) + .lastResult(current == null ? null : current.getLastResult()) + .images(current == null ? Map.of() : parseImages(current.getImagesJson())) + .backupFile(current == null ? null : current.getBackupFile()) + .online(current != null && isOnline(current.getLastSeenAt())) + .build(); + + saveVehicle(vehicleInfo); + return vehicleInfo; + } + + @Override + public VehicleInfo updateVehicle(String vehicleId, RegisterVehicleParam param) { + VehicleEntity existing = getEntity(vehicleId); + VehicleEntity duplicate = vehicleMapper.selectOne(new LambdaQueryWrapper() + .eq(VehicleEntity::getVehicleId, param.getVehicleId()) + .last("limit 1")); + if (duplicate != null && !duplicate.getId().equals(existing.getId())) { + throw new IllegalArgumentException("车辆 ID 已存在:" + param.getVehicleId()); + } + + VehicleInfo vehicleInfo = VehicleInfo.builder() + .vehicleId(param.getVehicleId()) + .vin(param.getVin()) + .currentRelease(param.getCurrentRelease()) + .currentBackendVersion(param.getCurrentBackendVersion()) + .currentFrontendVersion(param.getCurrentFrontendVersion()) + .currentRosVersion(param.getCurrentRosVersion()) + .lastSeenAt(existing.getLastSeenAt()) + .agentStatus(existing.getAgentStatus() == null ? AgentStatus.IDLE : AgentStatus.valueOf(existing.getAgentStatus())) + .targetRelease(existing.getTargetRelease()) + .lastResult(existing.getLastResult()) + .images(parseImages(existing.getImagesJson())) + .backupFile(existing.getBackupFile()) + .online(isOnline(existing.getLastSeenAt())) + .build(); + + saveVehicle(vehicleInfo, existing.getId()); + return vehicleInfo; + } + + @Override + public void deleteVehicle(String vehicleId) { + VehicleEntity existing = getEntity(vehicleId); + VehicleAssignmentEntity assignment = vehicleAssignmentMapper.selectOne(new LambdaQueryWrapper() + .eq(VehicleAssignmentEntity::getVehicleId, vehicleId) + .last("limit 1")); + if (assignment != null) { + throw new IllegalArgumentException("该车辆存在任务记录,无法删除:" + vehicleId); + } + vehicleMapper.deleteById(existing.getId()); + } + + @Override + public List listVehicles() { + return vehicleMapper.selectList(new LambdaQueryWrapper() + .orderByAsc(VehicleEntity::getVehicleId)) + .stream() + .map(this::toModel) + .toList(); + } + + @Override + public VehicleInfo saveHeartbeat(String vehicleId, + String vin, + String currentRelease, + AgentStatus agentStatus, + String targetRelease, + String lastResult, + Map images, + String backupFile) { + VehicleEntity current = vehicleMapper.selectOne(new LambdaQueryWrapper() + .eq(VehicleEntity::getVehicleId, vehicleId) + .last("limit 1")); + + VehicleInfo info = VehicleInfo.builder() + .vehicleId(vehicleId) + .vin(vin) + .currentRelease(currentRelease) + .currentBackendVersion(current == null ? null : current.getCurrentBackendVersion()) + .currentFrontendVersion(current == null ? null : current.getCurrentFrontendVersion()) + .currentRosVersion(current == null ? null : current.getCurrentRosVersion()) + .lastSeenAt(Instant.now()) + .agentStatus(agentStatus == null + ? (current == null || current.getAgentStatus() == null ? AgentStatus.IDLE : AgentStatus.valueOf(current.getAgentStatus())) + : agentStatus) + .targetRelease(targetRelease == null && current != null ? current.getTargetRelease() : targetRelease) + .lastResult(lastResult == null && current != null ? current.getLastResult() : lastResult) + .images(images == null ? (current == null ? Map.of() : parseImages(current.getImagesJson())) : new LinkedHashMap<>(images)) + .backupFile(backupFile == null && current != null ? current.getBackupFile() : backupFile) + .online(true) + .build(); + saveVehicle(info, current == null ? null : current.getId()); + return info; + } + + private void saveVehicle(VehicleInfo vehicleInfo, Long existingId) { + VehicleEntity entity = toEntity(vehicleInfo); + if (existingId == null) { + vehicleMapper.insert(entity); + } else { + entity.setId(existingId); + vehicleMapper.updateById(entity); + } + } + + private void saveVehicle(VehicleInfo vehicleInfo) { + VehicleEntity existing = vehicleMapper.selectOne(new LambdaQueryWrapper() + .eq(VehicleEntity::getVehicleId, vehicleInfo.getVehicleId()) + .last("limit 1")); + saveVehicle(vehicleInfo, existing == null ? null : existing.getId()); + } + + private VehicleEntity getEntity(String vehicleId) { + VehicleEntity entity = vehicleMapper.selectOne(new LambdaQueryWrapper() + .eq(VehicleEntity::getVehicleId, vehicleId) + .last("limit 1")); + if (entity == null) { + throw new IllegalArgumentException("Vehicle not found: " + vehicleId); + } + return entity; + } + + private VehicleInfo toModel(VehicleEntity entity) { + return VehicleInfo.builder() + .vehicleId(entity.getVehicleId()) + .vin(entity.getVin()) + .currentRelease(entity.getCurrentRelease()) + .currentBackendVersion(entity.getCurrentBackendVersion()) + .currentFrontendVersion(entity.getCurrentFrontendVersion()) + .currentRosVersion(entity.getCurrentRosVersion()) + .lastSeenAt(entity.getLastSeenAt()) + .agentStatus(entity.getAgentStatus() == null ? AgentStatus.IDLE : AgentStatus.valueOf(entity.getAgentStatus())) + .targetRelease(entity.getTargetRelease()) + .lastResult(entity.getLastResult()) + .images(parseImages(entity.getImagesJson())) + .backupFile(entity.getBackupFile()) + .online(isOnline(entity.getLastSeenAt())) + .build(); + } + + private VehicleEntity toEntity(VehicleInfo vehicleInfo) { + VehicleEntity entity = new VehicleEntity(); + entity.setVehicleId(vehicleInfo.getVehicleId()); + entity.setVin(vehicleInfo.getVin()); + entity.setCurrentRelease(vehicleInfo.getCurrentRelease()); + entity.setCurrentBackendVersion(vehicleInfo.getCurrentBackendVersion()); + entity.setCurrentFrontendVersion(vehicleInfo.getCurrentFrontendVersion()); + entity.setCurrentRosVersion(vehicleInfo.getCurrentRosVersion()); + entity.setLastSeenAt(vehicleInfo.getLastSeenAt()); + entity.setAgentStatus(vehicleInfo.getAgentStatus() == null ? null : vehicleInfo.getAgentStatus().name()); + entity.setTargetRelease(vehicleInfo.getTargetRelease()); + entity.setLastResult(vehicleInfo.getLastResult()); + entity.setImagesJson(writeImages(vehicleInfo.getImages())); + entity.setBackupFile(vehicleInfo.getBackupFile()); + return entity; + } + + private Map parseImages(String imagesJson) { + if (imagesJson == null || imagesJson.isBlank()) { + return Map.of(); + } + try { + return objectMapper.readValue(imagesJson, STRING_MAP_TYPE); + } catch (JsonProcessingException e) { + throw new IllegalStateException("Failed to parse imagesJson", e); + } + } + + private String writeImages(Map images) { + if (images == null || images.isEmpty()) { + return null; + } + try { + return objectMapper.writeValueAsString(images); + } catch (JsonProcessingException e) { + throw new IllegalStateException("Failed to serialize imagesJson", e); + } + } + + private boolean isOnline(Instant lastSeenAt) { + return lastSeenAt != null + && lastSeenAt.isAfter(Instant.now().minusSeconds(otaProperties.getHeartbeatTimeoutSeconds())); + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..06ed25a --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,55 @@ +spring: + application: + name: ota-server + datasource: + url: jdbc:mysql://127.0.0.1:3306/ota_server?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai + username: root + password: 123456 + driver-class-name: com.mysql.cj.jdbc.Driver + +server: + port: 8080 + +ota: + auth-token: f47ac10b-58cc-4372-a567-0e02b2c3d479 + heartbeat-timeout-seconds: 120 + cors: + allowed-origins: + - http://localhost:5173 + - http://127.0.0.1:5173 + allowed-methods: + - GET + - POST + - PUT + - DELETE + - OPTIONS + allowed-headers: + - "*" + allow-credentials: true + +sa-token: + token-name: X-OTA-TOKEN + token-prefix: "" + timeout: 1800 + active-timeout: -1 + is-concurrent: true + is-share: true + is-log: true + token-style: uuid + is-read-header: true + is-read-cookie: false + is-read-body: false + is-read-param: false + +mybatis-plus: + configuration: + map-underscore-to-camel-case: true + global-config: + db-config: + id-type: auto + +management: + endpoints: + web: + exposure: + include: health,info diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql new file mode 100644 index 0000000..c14f6c7 --- /dev/null +++ b/src/main/resources/schema.sql @@ -0,0 +1,90 @@ +CREATE DATABASE IF NOT EXISTS ota_server DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +USE ota_server; + +CREATE TABLE IF NOT EXISTS ota_release ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + release_version VARCHAR(128) NOT NULL UNIQUE, + release_notes TEXT NOT NULL, + upgrade_mode VARCHAR(32) NOT NULL, + published_at DATETIME(6) NOT NULL, + parking_required TINYINT(1) NOT NULL DEFAULT 0, + components_json JSON NOT NULL +); + +CREATE TABLE IF NOT EXISTS ota_vehicle ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + vehicle_id VARCHAR(64) NOT NULL UNIQUE, + vin VARCHAR(64) NOT NULL, + current_release VARCHAR(128) NOT NULL, + current_backend_version VARCHAR(128) NULL, + current_frontend_version VARCHAR(128) NULL, + current_ros_version VARCHAR(128) NULL, + last_seen_at DATETIME(6) NULL, + agent_status VARCHAR(32) NULL, + target_release VARCHAR(128) NULL, + last_result VARCHAR(512) NULL, + images_json JSON NULL, + backup_file VARCHAR(512) NULL +); + +CREATE TABLE IF NOT EXISTS ota_vehicle_assignment ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + vehicle_id VARCHAR(64) NOT NULL UNIQUE, + release_version VARCHAR(128) NOT NULL, + task_status VARCHAR(32) NOT NULL, + prompted_at DATETIME(6) NULL, + confirmed_at DATETIME(6) NULL, + started_at DATETIME(6) NULL, + finished_at DATETIME(6) NULL, + postpone_count INT NOT NULL DEFAULT 0, + last_message VARCHAR(512) NULL, + CONSTRAINT fk_assignment_release_version FOREIGN KEY (release_version) REFERENCES ota_release(release_version) +); + +CREATE TABLE IF NOT EXISTS sys_user ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + username VARCHAR(64) NOT NULL UNIQUE, + password VARCHAR(128) NOT NULL, + nickname VARCHAR(64) NOT NULL, + status TINYINT NOT NULL DEFAULT 1, + created_at DATETIME(6) NOT NULL, + updated_at DATETIME(6) NOT NULL +); + +CREATE TABLE IF NOT EXISTS sys_role ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + role_code VARCHAR(64) NOT NULL UNIQUE, + role_name VARCHAR(64) NOT NULL, + status TINYINT NOT NULL DEFAULT 1, + created_at DATETIME(6) NOT NULL, + updated_at DATETIME(6) NOT NULL +); + +CREATE TABLE IF NOT EXISTS sys_user_role ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + user_id BIGINT NOT NULL, + role_id BIGINT NOT NULL, + created_at DATETIME(6) NOT NULL, + UNIQUE KEY uk_user_role (user_id, role_id), + CONSTRAINT fk_user_role_user FOREIGN KEY (user_id) REFERENCES sys_user(id), + CONSTRAINT fk_user_role_role FOREIGN KEY (role_id) REFERENCES sys_role(id) +); + +INSERT INTO sys_role (id, role_code, role_name, status, created_at, updated_at) +VALUES + (1, 'SUPER_ADMIN', '系统管理员', 1, NOW(6), NOW(6)), + (2, 'ADMIN', '运维管理员', 1, NOW(6), NOW(6)), + (3, 'AGENT', '车端代理', 1, NOW(6), NOW(6)) +ON DUPLICATE KEY UPDATE role_name = VALUES(role_name), status = VALUES(status), updated_at = NOW(6); + +INSERT INTO sys_user (id, username, password, nickname, status, created_at, updated_at) +VALUES + (1, 'admin', 'e10adc3949ba59abbe56e057f20f883e', '超级管理员', 1, NOW(6), NOW(6)), + (2, 'agent', 'e10adc3949ba59abbe56e057f20f883e', '车端代理', 1, NOW(6), NOW(6)) +ON DUPLICATE KEY UPDATE password = VALUES(password), nickname = VALUES(nickname), status = VALUES(status), updated_at = NOW(6); + +INSERT INTO sys_user_role (user_id, role_id, created_at) +VALUES + (1, 1, NOW(6)), + (2, 3, NOW(6)) +ON DUPLICATE KEY UPDATE created_at = VALUES(created_at); diff --git a/target/classes/application.yml b/target/classes/application.yml new file mode 100644 index 0000000..06ed25a --- /dev/null +++ b/target/classes/application.yml @@ -0,0 +1,55 @@ +spring: + application: + name: ota-server + datasource: + url: jdbc:mysql://127.0.0.1:3306/ota_server?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai + username: root + password: 123456 + driver-class-name: com.mysql.cj.jdbc.Driver + +server: + port: 8080 + +ota: + auth-token: f47ac10b-58cc-4372-a567-0e02b2c3d479 + heartbeat-timeout-seconds: 120 + cors: + allowed-origins: + - http://localhost:5173 + - http://127.0.0.1:5173 + allowed-methods: + - GET + - POST + - PUT + - DELETE + - OPTIONS + allowed-headers: + - "*" + allow-credentials: true + +sa-token: + token-name: X-OTA-TOKEN + token-prefix: "" + timeout: 1800 + active-timeout: -1 + is-concurrent: true + is-share: true + is-log: true + token-style: uuid + is-read-header: true + is-read-cookie: false + is-read-body: false + is-read-param: false + +mybatis-plus: + configuration: + map-underscore-to-camel-case: true + global-config: + db-config: + id-type: auto + +management: + endpoints: + web: + exposure: + include: health,info diff --git a/target/classes/com/noblelift/ota/OtaServerApplication.class b/target/classes/com/noblelift/ota/OtaServerApplication.class new file mode 100644 index 0000000..e39df00 Binary files /dev/null and b/target/classes/com/noblelift/ota/OtaServerApplication.class differ diff --git a/target/classes/com/noblelift/ota/api/GlobalExceptionHandler.class b/target/classes/com/noblelift/ota/api/GlobalExceptionHandler.class new file mode 100644 index 0000000..e4afd0d Binary files /dev/null and b/target/classes/com/noblelift/ota/api/GlobalExceptionHandler.class differ diff --git a/target/classes/com/noblelift/ota/common/annotation/Log.class b/target/classes/com/noblelift/ota/common/annotation/Log.class new file mode 100644 index 0000000..bd8e105 Binary files /dev/null and b/target/classes/com/noblelift/ota/common/annotation/Log.class differ diff --git a/target/classes/com/noblelift/ota/common/aspect/LogAspect.class b/target/classes/com/noblelift/ota/common/aspect/LogAspect.class new file mode 100644 index 0000000..ee4be4d Binary files /dev/null and b/target/classes/com/noblelift/ota/common/aspect/LogAspect.class differ diff --git a/target/classes/com/noblelift/ota/common/dto/ApiMessage$ApiMessageBuilder.class b/target/classes/com/noblelift/ota/common/dto/ApiMessage$ApiMessageBuilder.class new file mode 100644 index 0000000..e0c27de Binary files /dev/null and b/target/classes/com/noblelift/ota/common/dto/ApiMessage$ApiMessageBuilder.class differ diff --git a/target/classes/com/noblelift/ota/common/dto/ApiMessage.class b/target/classes/com/noblelift/ota/common/dto/ApiMessage.class new file mode 100644 index 0000000..79fa860 Binary files /dev/null and b/target/classes/com/noblelift/ota/common/dto/ApiMessage.class differ diff --git a/target/classes/com/noblelift/ota/common/model/ReleaseManifest$ReleaseManifestBuilder.class b/target/classes/com/noblelift/ota/common/model/ReleaseManifest$ReleaseManifestBuilder.class new file mode 100644 index 0000000..52e4e16 Binary files /dev/null and b/target/classes/com/noblelift/ota/common/model/ReleaseManifest$ReleaseManifestBuilder.class differ diff --git a/target/classes/com/noblelift/ota/common/model/ReleaseManifest.class b/target/classes/com/noblelift/ota/common/model/ReleaseManifest.class new file mode 100644 index 0000000..bda68e1 Binary files /dev/null and b/target/classes/com/noblelift/ota/common/model/ReleaseManifest.class differ diff --git a/target/classes/com/noblelift/ota/common/model/VehicleAssignment$VehicleAssignmentBuilder.class b/target/classes/com/noblelift/ota/common/model/VehicleAssignment$VehicleAssignmentBuilder.class new file mode 100644 index 0000000..d67521c Binary files /dev/null and b/target/classes/com/noblelift/ota/common/model/VehicleAssignment$VehicleAssignmentBuilder.class differ diff --git a/target/classes/com/noblelift/ota/common/model/VehicleAssignment.class b/target/classes/com/noblelift/ota/common/model/VehicleAssignment.class new file mode 100644 index 0000000..4969e3b Binary files /dev/null and b/target/classes/com/noblelift/ota/common/model/VehicleAssignment.class differ diff --git a/target/classes/com/noblelift/ota/common/model/VehicleInfo$VehicleInfoBuilder.class b/target/classes/com/noblelift/ota/common/model/VehicleInfo$VehicleInfoBuilder.class new file mode 100644 index 0000000..5c30333 Binary files /dev/null and b/target/classes/com/noblelift/ota/common/model/VehicleInfo$VehicleInfoBuilder.class differ diff --git a/target/classes/com/noblelift/ota/common/model/VehicleInfo.class b/target/classes/com/noblelift/ota/common/model/VehicleInfo.class new file mode 100644 index 0000000..33ecdd0 Binary files /dev/null and b/target/classes/com/noblelift/ota/common/model/VehicleInfo.class differ diff --git a/target/classes/com/noblelift/ota/common/util/ReleaseVersionComparator.class b/target/classes/com/noblelift/ota/common/util/ReleaseVersionComparator.class new file mode 100644 index 0000000..243b55b Binary files /dev/null and b/target/classes/com/noblelift/ota/common/util/ReleaseVersionComparator.class differ diff --git a/target/classes/com/noblelift/ota/config/AgentTokenAuthenticator.class b/target/classes/com/noblelift/ota/config/AgentTokenAuthenticator.class new file mode 100644 index 0000000..3f879d6 Binary files /dev/null and b/target/classes/com/noblelift/ota/config/AgentTokenAuthenticator.class differ diff --git a/target/classes/com/noblelift/ota/config/OtaConfiguration.class b/target/classes/com/noblelift/ota/config/OtaConfiguration.class new file mode 100644 index 0000000..03d4886 Binary files /dev/null and b/target/classes/com/noblelift/ota/config/OtaConfiguration.class differ diff --git a/target/classes/com/noblelift/ota/config/OtaProperties$Cors.class b/target/classes/com/noblelift/ota/config/OtaProperties$Cors.class new file mode 100644 index 0000000..28c3e8a Binary files /dev/null and b/target/classes/com/noblelift/ota/config/OtaProperties$Cors.class differ diff --git a/target/classes/com/noblelift/ota/config/OtaProperties.class b/target/classes/com/noblelift/ota/config/OtaProperties.class new file mode 100644 index 0000000..13c8cc5 Binary files /dev/null and b/target/classes/com/noblelift/ota/config/OtaProperties.class differ diff --git a/target/classes/com/noblelift/ota/config/SaTokenConfigure.class b/target/classes/com/noblelift/ota/config/SaTokenConfigure.class new file mode 100644 index 0000000..6588170 Binary files /dev/null and b/target/classes/com/noblelift/ota/config/SaTokenConfigure.class differ diff --git a/target/classes/com/noblelift/ota/config/SaTokenExceptionHandler.class b/target/classes/com/noblelift/ota/config/SaTokenExceptionHandler.class new file mode 100644 index 0000000..1cef77b Binary files /dev/null and b/target/classes/com/noblelift/ota/config/SaTokenExceptionHandler.class differ diff --git a/target/classes/com/noblelift/ota/domain/AgentStatus.class b/target/classes/com/noblelift/ota/domain/AgentStatus.class new file mode 100644 index 0000000..590abe1 Binary files /dev/null and b/target/classes/com/noblelift/ota/domain/AgentStatus.class differ diff --git a/target/classes/com/noblelift/ota/domain/TaskStatus.class b/target/classes/com/noblelift/ota/domain/TaskStatus.class new file mode 100644 index 0000000..6d2f81d Binary files /dev/null and b/target/classes/com/noblelift/ota/domain/TaskStatus.class differ diff --git a/target/classes/com/noblelift/ota/domain/UpgradeMode.class b/target/classes/com/noblelift/ota/domain/UpgradeMode.class new file mode 100644 index 0000000..6f9d9b6 Binary files /dev/null and b/target/classes/com/noblelift/ota/domain/UpgradeMode.class differ diff --git a/target/classes/com/noblelift/ota/module/agent/controller/AgentController.class b/target/classes/com/noblelift/ota/module/agent/controller/AgentController.class new file mode 100644 index 0000000..3476b2a Binary files /dev/null and b/target/classes/com/noblelift/ota/module/agent/controller/AgentController.class differ diff --git a/target/classes/com/noblelift/ota/module/agent/dto/HeartbeatResponse.class b/target/classes/com/noblelift/ota/module/agent/dto/HeartbeatResponse.class new file mode 100644 index 0000000..ef2a1e0 Binary files /dev/null and b/target/classes/com/noblelift/ota/module/agent/dto/HeartbeatResponse.class differ diff --git a/target/classes/com/noblelift/ota/module/agent/dto/UpdateCheckResponse.class b/target/classes/com/noblelift/ota/module/agent/dto/UpdateCheckResponse.class new file mode 100644 index 0000000..532867d Binary files /dev/null and b/target/classes/com/noblelift/ota/module/agent/dto/UpdateCheckResponse.class differ diff --git a/target/classes/com/noblelift/ota/module/agent/param/HeartbeatParam.class b/target/classes/com/noblelift/ota/module/agent/param/HeartbeatParam.class new file mode 100644 index 0000000..0aeec27 Binary files /dev/null and b/target/classes/com/noblelift/ota/module/agent/param/HeartbeatParam.class differ diff --git a/target/classes/com/noblelift/ota/module/agent/param/ReportParam.class b/target/classes/com/noblelift/ota/module/agent/param/ReportParam.class new file mode 100644 index 0000000..8a43ffd Binary files /dev/null and b/target/classes/com/noblelift/ota/module/agent/param/ReportParam.class differ diff --git a/target/classes/com/noblelift/ota/module/agent/param/UpdateCheckParam.class b/target/classes/com/noblelift/ota/module/agent/param/UpdateCheckParam.class new file mode 100644 index 0000000..352de9b Binary files /dev/null and b/target/classes/com/noblelift/ota/module/agent/param/UpdateCheckParam.class differ diff --git a/target/classes/com/noblelift/ota/module/agent/service/AgentService.class b/target/classes/com/noblelift/ota/module/agent/service/AgentService.class new file mode 100644 index 0000000..3c7d06c Binary files /dev/null and b/target/classes/com/noblelift/ota/module/agent/service/AgentService.class differ diff --git a/target/classes/com/noblelift/ota/module/agent/service/impl/AgentServiceImpl.class b/target/classes/com/noblelift/ota/module/agent/service/impl/AgentServiceImpl.class new file mode 100644 index 0000000..19f971d Binary files /dev/null and b/target/classes/com/noblelift/ota/module/agent/service/impl/AgentServiceImpl.class differ diff --git a/target/classes/com/noblelift/ota/module/assignment/controller/AssignmentController.class b/target/classes/com/noblelift/ota/module/assignment/controller/AssignmentController.class new file mode 100644 index 0000000..32172bc Binary files /dev/null and b/target/classes/com/noblelift/ota/module/assignment/controller/AssignmentController.class differ diff --git a/target/classes/com/noblelift/ota/module/assignment/dto/AssignmentView.class b/target/classes/com/noblelift/ota/module/assignment/dto/AssignmentView.class new file mode 100644 index 0000000..13e4b3d Binary files /dev/null and b/target/classes/com/noblelift/ota/module/assignment/dto/AssignmentView.class differ diff --git a/target/classes/com/noblelift/ota/module/assignment/entity/VehicleAssignmentEntity.class b/target/classes/com/noblelift/ota/module/assignment/entity/VehicleAssignmentEntity.class new file mode 100644 index 0000000..e1682b9 Binary files /dev/null and b/target/classes/com/noblelift/ota/module/assignment/entity/VehicleAssignmentEntity.class differ diff --git a/target/classes/com/noblelift/ota/module/assignment/mapper/VehicleAssignmentMapper.class b/target/classes/com/noblelift/ota/module/assignment/mapper/VehicleAssignmentMapper.class new file mode 100644 index 0000000..6870395 Binary files /dev/null and b/target/classes/com/noblelift/ota/module/assignment/mapper/VehicleAssignmentMapper.class differ diff --git a/target/classes/com/noblelift/ota/module/assignment/param/AssignReleaseParam.class b/target/classes/com/noblelift/ota/module/assignment/param/AssignReleaseParam.class new file mode 100644 index 0000000..f719144 Binary files /dev/null and b/target/classes/com/noblelift/ota/module/assignment/param/AssignReleaseParam.class differ diff --git a/target/classes/com/noblelift/ota/module/assignment/service/AssignmentService.class b/target/classes/com/noblelift/ota/module/assignment/service/AssignmentService.class new file mode 100644 index 0000000..567834d Binary files /dev/null and b/target/classes/com/noblelift/ota/module/assignment/service/AssignmentService.class differ diff --git a/target/classes/com/noblelift/ota/module/assignment/service/impl/AssignmentServiceImpl.class b/target/classes/com/noblelift/ota/module/assignment/service/impl/AssignmentServiceImpl.class new file mode 100644 index 0000000..fd6b753 Binary files /dev/null and b/target/classes/com/noblelift/ota/module/assignment/service/impl/AssignmentServiceImpl.class differ diff --git a/target/classes/com/noblelift/ota/module/auth/controller/AuthController.class b/target/classes/com/noblelift/ota/module/auth/controller/AuthController.class new file mode 100644 index 0000000..340f767 Binary files /dev/null and b/target/classes/com/noblelift/ota/module/auth/controller/AuthController.class differ diff --git a/target/classes/com/noblelift/ota/module/auth/dto/LoginResponse.class b/target/classes/com/noblelift/ota/module/auth/dto/LoginResponse.class new file mode 100644 index 0000000..420a6de Binary files /dev/null and b/target/classes/com/noblelift/ota/module/auth/dto/LoginResponse.class differ diff --git a/target/classes/com/noblelift/ota/module/auth/entity/SysRoleEntity.class b/target/classes/com/noblelift/ota/module/auth/entity/SysRoleEntity.class new file mode 100644 index 0000000..2e1fee3 Binary files /dev/null and b/target/classes/com/noblelift/ota/module/auth/entity/SysRoleEntity.class differ diff --git a/target/classes/com/noblelift/ota/module/auth/entity/SysUserEntity.class b/target/classes/com/noblelift/ota/module/auth/entity/SysUserEntity.class new file mode 100644 index 0000000..3533e09 Binary files /dev/null and b/target/classes/com/noblelift/ota/module/auth/entity/SysUserEntity.class differ diff --git a/target/classes/com/noblelift/ota/module/auth/entity/SysUserRoleEntity.class b/target/classes/com/noblelift/ota/module/auth/entity/SysUserRoleEntity.class new file mode 100644 index 0000000..1ce6f35 Binary files /dev/null and b/target/classes/com/noblelift/ota/module/auth/entity/SysUserRoleEntity.class differ diff --git a/target/classes/com/noblelift/ota/module/auth/mapper/SysRoleMapper.class b/target/classes/com/noblelift/ota/module/auth/mapper/SysRoleMapper.class new file mode 100644 index 0000000..1452d89 Binary files /dev/null and b/target/classes/com/noblelift/ota/module/auth/mapper/SysRoleMapper.class differ diff --git a/target/classes/com/noblelift/ota/module/auth/mapper/SysUserMapper.class b/target/classes/com/noblelift/ota/module/auth/mapper/SysUserMapper.class new file mode 100644 index 0000000..b640ecb Binary files /dev/null and b/target/classes/com/noblelift/ota/module/auth/mapper/SysUserMapper.class differ diff --git a/target/classes/com/noblelift/ota/module/auth/mapper/SysUserRoleMapper.class b/target/classes/com/noblelift/ota/module/auth/mapper/SysUserRoleMapper.class new file mode 100644 index 0000000..8e46ca9 Binary files /dev/null and b/target/classes/com/noblelift/ota/module/auth/mapper/SysUserRoleMapper.class differ diff --git a/target/classes/com/noblelift/ota/module/auth/param/LoginParam.class b/target/classes/com/noblelift/ota/module/auth/param/LoginParam.class new file mode 100644 index 0000000..e23f32c Binary files /dev/null and b/target/classes/com/noblelift/ota/module/auth/param/LoginParam.class differ diff --git a/target/classes/com/noblelift/ota/module/auth/service/AuthService.class b/target/classes/com/noblelift/ota/module/auth/service/AuthService.class new file mode 100644 index 0000000..1eebb8b Binary files /dev/null and b/target/classes/com/noblelift/ota/module/auth/service/AuthService.class differ diff --git a/target/classes/com/noblelift/ota/module/auth/service/impl/AuthServiceImpl.class b/target/classes/com/noblelift/ota/module/auth/service/impl/AuthServiceImpl.class new file mode 100644 index 0000000..af00b5b Binary files /dev/null and b/target/classes/com/noblelift/ota/module/auth/service/impl/AuthServiceImpl.class differ diff --git a/target/classes/com/noblelift/ota/module/auth/service/impl/StpInterfaceImpl.class b/target/classes/com/noblelift/ota/module/auth/service/impl/StpInterfaceImpl.class new file mode 100644 index 0000000..84b5433 Binary files /dev/null and b/target/classes/com/noblelift/ota/module/auth/service/impl/StpInterfaceImpl.class differ diff --git a/target/classes/com/noblelift/ota/module/release/controller/ReleaseController.class b/target/classes/com/noblelift/ota/module/release/controller/ReleaseController.class new file mode 100644 index 0000000..a31402f Binary files /dev/null and b/target/classes/com/noblelift/ota/module/release/controller/ReleaseController.class differ diff --git a/target/classes/com/noblelift/ota/module/release/dto/ReleaseView.class b/target/classes/com/noblelift/ota/module/release/dto/ReleaseView.class new file mode 100644 index 0000000..aee9c2e Binary files /dev/null and b/target/classes/com/noblelift/ota/module/release/dto/ReleaseView.class differ diff --git a/target/classes/com/noblelift/ota/module/release/entity/ReleaseEntity.class b/target/classes/com/noblelift/ota/module/release/entity/ReleaseEntity.class new file mode 100644 index 0000000..9d47919 Binary files /dev/null and b/target/classes/com/noblelift/ota/module/release/entity/ReleaseEntity.class differ diff --git a/target/classes/com/noblelift/ota/module/release/mapper/ReleaseMapper.class b/target/classes/com/noblelift/ota/module/release/mapper/ReleaseMapper.class new file mode 100644 index 0000000..5e61efd Binary files /dev/null and b/target/classes/com/noblelift/ota/module/release/mapper/ReleaseMapper.class differ diff --git a/target/classes/com/noblelift/ota/module/release/param/CreateReleaseParam.class b/target/classes/com/noblelift/ota/module/release/param/CreateReleaseParam.class new file mode 100644 index 0000000..dfa3991 Binary files /dev/null and b/target/classes/com/noblelift/ota/module/release/param/CreateReleaseParam.class differ diff --git a/target/classes/com/noblelift/ota/module/release/service/ReleaseService.class b/target/classes/com/noblelift/ota/module/release/service/ReleaseService.class new file mode 100644 index 0000000..bb67ffe Binary files /dev/null and b/target/classes/com/noblelift/ota/module/release/service/ReleaseService.class differ diff --git a/target/classes/com/noblelift/ota/module/release/service/impl/ReleaseServiceImpl$1.class b/target/classes/com/noblelift/ota/module/release/service/impl/ReleaseServiceImpl$1.class new file mode 100644 index 0000000..3659364 Binary files /dev/null and b/target/classes/com/noblelift/ota/module/release/service/impl/ReleaseServiceImpl$1.class differ diff --git a/target/classes/com/noblelift/ota/module/release/service/impl/ReleaseServiceImpl.class b/target/classes/com/noblelift/ota/module/release/service/impl/ReleaseServiceImpl.class new file mode 100644 index 0000000..bb5cae5 Binary files /dev/null and b/target/classes/com/noblelift/ota/module/release/service/impl/ReleaseServiceImpl.class differ diff --git a/target/classes/com/noblelift/ota/module/vehicle/controller/VehicleController.class b/target/classes/com/noblelift/ota/module/vehicle/controller/VehicleController.class new file mode 100644 index 0000000..8e2d7bd Binary files /dev/null and b/target/classes/com/noblelift/ota/module/vehicle/controller/VehicleController.class differ diff --git a/target/classes/com/noblelift/ota/module/vehicle/dto/VehicleView.class b/target/classes/com/noblelift/ota/module/vehicle/dto/VehicleView.class new file mode 100644 index 0000000..d2be319 Binary files /dev/null and b/target/classes/com/noblelift/ota/module/vehicle/dto/VehicleView.class differ diff --git a/target/classes/com/noblelift/ota/module/vehicle/entity/VehicleEntity.class b/target/classes/com/noblelift/ota/module/vehicle/entity/VehicleEntity.class new file mode 100644 index 0000000..78783e2 Binary files /dev/null and b/target/classes/com/noblelift/ota/module/vehicle/entity/VehicleEntity.class differ diff --git a/target/classes/com/noblelift/ota/module/vehicle/mapper/VehicleMapper.class b/target/classes/com/noblelift/ota/module/vehicle/mapper/VehicleMapper.class new file mode 100644 index 0000000..2a56644 Binary files /dev/null and b/target/classes/com/noblelift/ota/module/vehicle/mapper/VehicleMapper.class differ diff --git a/target/classes/com/noblelift/ota/module/vehicle/param/RegisterVehicleParam.class b/target/classes/com/noblelift/ota/module/vehicle/param/RegisterVehicleParam.class new file mode 100644 index 0000000..ac24416 Binary files /dev/null and b/target/classes/com/noblelift/ota/module/vehicle/param/RegisterVehicleParam.class differ diff --git a/target/classes/com/noblelift/ota/module/vehicle/service/VehicleService.class b/target/classes/com/noblelift/ota/module/vehicle/service/VehicleService.class new file mode 100644 index 0000000..8dc6b0a Binary files /dev/null and b/target/classes/com/noblelift/ota/module/vehicle/service/VehicleService.class differ diff --git a/target/classes/com/noblelift/ota/module/vehicle/service/impl/VehicleServiceImpl$1.class b/target/classes/com/noblelift/ota/module/vehicle/service/impl/VehicleServiceImpl$1.class new file mode 100644 index 0000000..5695687 Binary files /dev/null and b/target/classes/com/noblelift/ota/module/vehicle/service/impl/VehicleServiceImpl$1.class differ diff --git a/target/classes/com/noblelift/ota/module/vehicle/service/impl/VehicleServiceImpl.class b/target/classes/com/noblelift/ota/module/vehicle/service/impl/VehicleServiceImpl.class new file mode 100644 index 0000000..8d25a0b Binary files /dev/null and b/target/classes/com/noblelift/ota/module/vehicle/service/impl/VehicleServiceImpl.class differ diff --git a/target/classes/schema.sql b/target/classes/schema.sql new file mode 100644 index 0000000..c14f6c7 --- /dev/null +++ b/target/classes/schema.sql @@ -0,0 +1,90 @@ +CREATE DATABASE IF NOT EXISTS ota_server DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +USE ota_server; + +CREATE TABLE IF NOT EXISTS ota_release ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + release_version VARCHAR(128) NOT NULL UNIQUE, + release_notes TEXT NOT NULL, + upgrade_mode VARCHAR(32) NOT NULL, + published_at DATETIME(6) NOT NULL, + parking_required TINYINT(1) NOT NULL DEFAULT 0, + components_json JSON NOT NULL +); + +CREATE TABLE IF NOT EXISTS ota_vehicle ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + vehicle_id VARCHAR(64) NOT NULL UNIQUE, + vin VARCHAR(64) NOT NULL, + current_release VARCHAR(128) NOT NULL, + current_backend_version VARCHAR(128) NULL, + current_frontend_version VARCHAR(128) NULL, + current_ros_version VARCHAR(128) NULL, + last_seen_at DATETIME(6) NULL, + agent_status VARCHAR(32) NULL, + target_release VARCHAR(128) NULL, + last_result VARCHAR(512) NULL, + images_json JSON NULL, + backup_file VARCHAR(512) NULL +); + +CREATE TABLE IF NOT EXISTS ota_vehicle_assignment ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + vehicle_id VARCHAR(64) NOT NULL UNIQUE, + release_version VARCHAR(128) NOT NULL, + task_status VARCHAR(32) NOT NULL, + prompted_at DATETIME(6) NULL, + confirmed_at DATETIME(6) NULL, + started_at DATETIME(6) NULL, + finished_at DATETIME(6) NULL, + postpone_count INT NOT NULL DEFAULT 0, + last_message VARCHAR(512) NULL, + CONSTRAINT fk_assignment_release_version FOREIGN KEY (release_version) REFERENCES ota_release(release_version) +); + +CREATE TABLE IF NOT EXISTS sys_user ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + username VARCHAR(64) NOT NULL UNIQUE, + password VARCHAR(128) NOT NULL, + nickname VARCHAR(64) NOT NULL, + status TINYINT NOT NULL DEFAULT 1, + created_at DATETIME(6) NOT NULL, + updated_at DATETIME(6) NOT NULL +); + +CREATE TABLE IF NOT EXISTS sys_role ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + role_code VARCHAR(64) NOT NULL UNIQUE, + role_name VARCHAR(64) NOT NULL, + status TINYINT NOT NULL DEFAULT 1, + created_at DATETIME(6) NOT NULL, + updated_at DATETIME(6) NOT NULL +); + +CREATE TABLE IF NOT EXISTS sys_user_role ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + user_id BIGINT NOT NULL, + role_id BIGINT NOT NULL, + created_at DATETIME(6) NOT NULL, + UNIQUE KEY uk_user_role (user_id, role_id), + CONSTRAINT fk_user_role_user FOREIGN KEY (user_id) REFERENCES sys_user(id), + CONSTRAINT fk_user_role_role FOREIGN KEY (role_id) REFERENCES sys_role(id) +); + +INSERT INTO sys_role (id, role_code, role_name, status, created_at, updated_at) +VALUES + (1, 'SUPER_ADMIN', '系统管理员', 1, NOW(6), NOW(6)), + (2, 'ADMIN', '运维管理员', 1, NOW(6), NOW(6)), + (3, 'AGENT', '车端代理', 1, NOW(6), NOW(6)) +ON DUPLICATE KEY UPDATE role_name = VALUES(role_name), status = VALUES(status), updated_at = NOW(6); + +INSERT INTO sys_user (id, username, password, nickname, status, created_at, updated_at) +VALUES + (1, 'admin', 'e10adc3949ba59abbe56e057f20f883e', '超级管理员', 1, NOW(6), NOW(6)), + (2, 'agent', 'e10adc3949ba59abbe56e057f20f883e', '车端代理', 1, NOW(6), NOW(6)) +ON DUPLICATE KEY UPDATE password = VALUES(password), nickname = VALUES(nickname), status = VALUES(status), updated_at = NOW(6); + +INSERT INTO sys_user_role (user_id, role_id, created_at) +VALUES + (1, 1, NOW(6)), + (2, 3, NOW(6)) +ON DUPLICATE KEY UPDATE created_at = VALUES(created_at); diff --git a/target/maven-archiver/pom.properties b/target/maven-archiver/pom.properties new file mode 100644 index 0000000..6f917d4 --- /dev/null +++ b/target/maven-archiver/pom.properties @@ -0,0 +1,3 @@ +artifactId=ota-server +groupId=com.noblelift +version=0.0.1-SNAPSHOT diff --git a/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst new file mode 100644 index 0000000..e69de29 diff --git a/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst new file mode 100644 index 0000000..99d2f1d --- /dev/null +++ b/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst @@ -0,0 +1,58 @@ +D:\Procedure\noblelift\hangzhou\OTA-Server\src\main\java\com\noblelift\ota\module\vehicle\param\RegisterVehicleParam.java +D:\Procedure\noblelift\hangzhou\OTA-Server\src\main\java\com\noblelift\ota\config\SaTokenConfigure.java +D:\Procedure\noblelift\hangzhou\OTA-Server\src\main\java\com\noblelift\ota\config\OtaProperties.java +D:\Procedure\noblelift\hangzhou\OTA-Server\src\main\java\com\noblelift\ota\module\agent\service\AgentService.java +D:\Procedure\noblelift\hangzhou\OTA-Server\src\main\java\com\noblelift\ota\module\assignment\dto\AssignmentView.java +D:\Procedure\noblelift\hangzhou\OTA-Server\src\main\java\com\noblelift\ota\module\agent\service\impl\AgentServiceImpl.java +D:\Procedure\noblelift\hangzhou\OTA-Server\src\main\java\com\noblelift\ota\api\GlobalExceptionHandler.java +D:\Procedure\noblelift\hangzhou\OTA-Server\src\main\java\com\noblelift\ota\module\agent\param\ReportParam.java +D:\Procedure\noblelift\hangzhou\OTA-Server\src\main\java\com\noblelift\ota\module\assignment\param\AssignReleaseParam.java +D:\Procedure\noblelift\hangzhou\OTA-Server\src\main\java\com\noblelift\ota\module\assignment\service\impl\AssignmentServiceImpl.java +D:\Procedure\noblelift\hangzhou\OTA-Server\src\main\java\com\noblelift\ota\module\vehicle\service\VehicleService.java +D:\Procedure\noblelift\hangzhou\OTA-Server\src\main\java\com\noblelift\ota\module\assignment\mapper\VehicleAssignmentMapper.java +D:\Procedure\noblelift\hangzhou\OTA-Server\src\main\java\com\noblelift\ota\module\auth\mapper\SysUserMapper.java +D:\Procedure\noblelift\hangzhou\OTA-Server\src\main\java\com\noblelift\ota\module\auth\service\impl\StpInterfaceImpl.java +D:\Procedure\noblelift\hangzhou\OTA-Server\src\main\java\com\noblelift\ota\module\vehicle\entity\VehicleEntity.java +D:\Procedure\noblelift\hangzhou\OTA-Server\src\main\java\com\noblelift\ota\common\aspect\LogAspect.java +D:\Procedure\noblelift\hangzhou\OTA-Server\src\main\java\com\noblelift\ota\domain\TaskStatus.java +D:\Procedure\noblelift\hangzhou\OTA-Server\src\main\java\com\noblelift\ota\module\assignment\controller\AssignmentController.java +D:\Procedure\noblelift\hangzhou\OTA-Server\src\main\java\com\noblelift\ota\module\release\param\CreateReleaseParam.java +D:\Procedure\noblelift\hangzhou\OTA-Server\src\main\java\com\noblelift\ota\module\agent\dto\UpdateCheckResponse.java +D:\Procedure\noblelift\hangzhou\OTA-Server\src\main\java\com\noblelift\ota\module\release\controller\ReleaseController.java +D:\Procedure\noblelift\hangzhou\OTA-Server\src\main\java\com\noblelift\ota\module\auth\mapper\SysRoleMapper.java +D:\Procedure\noblelift\hangzhou\OTA-Server\src\main\java\com\noblelift\ota\module\assignment\entity\VehicleAssignmentEntity.java +D:\Procedure\noblelift\hangzhou\OTA-Server\src\main\java\com\noblelift\ota\module\release\dto\ReleaseView.java +D:\Procedure\noblelift\hangzhou\OTA-Server\src\main\java\com\noblelift\ota\module\release\service\ReleaseService.java +D:\Procedure\noblelift\hangzhou\OTA-Server\src\main\java\com\noblelift\ota\OtaServerApplication.java +D:\Procedure\noblelift\hangzhou\OTA-Server\src\main\java\com\noblelift\ota\module\release\entity\ReleaseEntity.java +D:\Procedure\noblelift\hangzhou\OTA-Server\src\main\java\com\noblelift\ota\domain\AgentStatus.java +D:\Procedure\noblelift\hangzhou\OTA-Server\src\main\java\com\noblelift\ota\config\SaTokenExceptionHandler.java +D:\Procedure\noblelift\hangzhou\OTA-Server\src\main\java\com\noblelift\ota\config\AgentTokenAuthenticator.java +D:\Procedure\noblelift\hangzhou\OTA-Server\src\main\java\com\noblelift\ota\module\agent\dto\HeartbeatResponse.java +D:\Procedure\noblelift\hangzhou\OTA-Server\src\main\java\com\noblelift\ota\module\auth\controller\AuthController.java +D:\Procedure\noblelift\hangzhou\OTA-Server\src\main\java\com\noblelift\ota\common\model\ReleaseManifest.java +D:\Procedure\noblelift\hangzhou\OTA-Server\src\main\java\com\noblelift\ota\module\release\service\impl\ReleaseServiceImpl.java +D:\Procedure\noblelift\hangzhou\OTA-Server\src\main\java\com\noblelift\ota\module\agent\param\UpdateCheckParam.java +D:\Procedure\noblelift\hangzhou\OTA-Server\src\main\java\com\noblelift\ota\common\dto\ApiMessage.java +D:\Procedure\noblelift\hangzhou\OTA-Server\src\main\java\com\noblelift\ota\module\agent\controller\AgentController.java +D:\Procedure\noblelift\hangzhou\OTA-Server\src\main\java\com\noblelift\ota\module\auth\dto\LoginResponse.java +D:\Procedure\noblelift\hangzhou\OTA-Server\src\main\java\com\noblelift\ota\module\auth\param\LoginParam.java +D:\Procedure\noblelift\hangzhou\OTA-Server\src\main\java\com\noblelift\ota\common\model\VehicleInfo.java +D:\Procedure\noblelift\hangzhou\OTA-Server\src\main\java\com\noblelift\ota\module\agent\param\HeartbeatParam.java +D:\Procedure\noblelift\hangzhou\OTA-Server\src\main\java\com\noblelift\ota\module\assignment\service\AssignmentService.java +D:\Procedure\noblelift\hangzhou\OTA-Server\src\main\java\com\noblelift\ota\module\auth\service\AuthService.java +D:\Procedure\noblelift\hangzhou\OTA-Server\src\main\java\com\noblelift\ota\domain\UpgradeMode.java +D:\Procedure\noblelift\hangzhou\OTA-Server\src\main\java\com\noblelift\ota\module\vehicle\service\impl\VehicleServiceImpl.java +D:\Procedure\noblelift\hangzhou\OTA-Server\src\main\java\com\noblelift\ota\module\vehicle\controller\VehicleController.java +D:\Procedure\noblelift\hangzhou\OTA-Server\src\main\java\com\noblelift\ota\module\vehicle\mapper\VehicleMapper.java +D:\Procedure\noblelift\hangzhou\OTA-Server\src\main\java\com\noblelift\ota\common\model\VehicleAssignment.java +D:\Procedure\noblelift\hangzhou\OTA-Server\src\main\java\com\noblelift\ota\module\auth\mapper\SysUserRoleMapper.java +D:\Procedure\noblelift\hangzhou\OTA-Server\src\main\java\com\noblelift\ota\module\auth\entity\SysUserEntity.java +D:\Procedure\noblelift\hangzhou\OTA-Server\src\main\java\com\noblelift\ota\module\auth\entity\SysUserRoleEntity.java +D:\Procedure\noblelift\hangzhou\OTA-Server\src\main\java\com\noblelift\ota\module\auth\service\impl\AuthServiceImpl.java +D:\Procedure\noblelift\hangzhou\OTA-Server\src\main\java\com\noblelift\ota\common\annotation\Log.java +D:\Procedure\noblelift\hangzhou\OTA-Server\src\main\java\com\noblelift\ota\config\OtaConfiguration.java +D:\Procedure\noblelift\hangzhou\OTA-Server\src\main\java\com\noblelift\ota\common\util\ReleaseVersionComparator.java +D:\Procedure\noblelift\hangzhou\OTA-Server\src\main\java\com\noblelift\ota\module\release\mapper\ReleaseMapper.java +D:\Procedure\noblelift\hangzhou\OTA-Server\src\main\java\com\noblelift\ota\module\auth\entity\SysRoleEntity.java +D:\Procedure\noblelift\hangzhou\OTA-Server\src\main\java\com\noblelift\ota\module\vehicle\dto\VehicleView.java diff --git a/target/ota-server-0.0.1-SNAPSHOT.jar b/target/ota-server-0.0.1-SNAPSHOT.jar new file mode 100644 index 0000000..52bc378 Binary files /dev/null and b/target/ota-server-0.0.1-SNAPSHOT.jar differ diff --git a/target/ota-server-0.0.1-SNAPSHOT.jar.original b/target/ota-server-0.0.1-SNAPSHOT.jar.original new file mode 100644 index 0000000..af91dd1 Binary files /dev/null and b/target/ota-server-0.0.1-SNAPSHOT.jar.original differ