添加模块

This commit is contained in:
孙小云 2025-12-22 13:29:22 +08:00
parent c3257afd4a
commit a0b821dd85
181 changed files with 10911 additions and 0 deletions

View File

@ -0,0 +1,40 @@
package com.tuoheng.airport.airline.application.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* 航线创建请求 DTO
*
* @author tuoheng
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AirlineCreateRequest {
/**
* 航线编码
*/
private String airlineCode;
/**
* 航线名称
*/
private String airlineName;
/**
* 描述
*/
private String description;
/**
* 航点列表
*/
private List<WaypointRequest> waypoints;
}

View File

@ -0,0 +1,38 @@
package com.tuoheng.airport.airline.application.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 航线查询请求 DTO
*
* @author tuoheng
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AirlineQueryRequest {
/**
* 航线名称模糊查询
*/
private String airlineName;
/**
* 航线状态
*/
private String status;
/**
* 创建人ID
*/
private Long creatorId;
/**
* 租户ID
*/
private Long tenantId;
}

View File

@ -0,0 +1,36 @@
package com.tuoheng.airport.airline.application.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
/**
* 航线响应 DTO
*
* @author tuoheng
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AirlineResponse {
private Long id;
private String airlineCode;
private String airlineName;
private String description;
private String status;
private String fileUrl;
private Double totalDistance;
private Long estimatedDuration;
private Long tenantId;
private Long creatorId;
private Long reviewerId;
private String reviewComment;
private LocalDateTime reviewTime;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}

View File

@ -0,0 +1,8 @@
package com.tuoheng.airport.airline.application.dto;
import lombok.Data;
@Data
public class AirlineStatisticsResponse {
// TODO: 添加字段
}

View File

@ -0,0 +1,33 @@
package com.tuoheng.airport.airline.application.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 航线更新请求 DTO
*
* @author tuoheng
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AirlineUpdateRequest {
/**
* 航线ID
*/
private Long id;
/**
* 航线名称
*/
private String airlineName;
/**
* 描述
*/
private String description;
}

View File

@ -0,0 +1,34 @@
package com.tuoheng.airport.airline.application.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.web.multipart.MultipartFile;
/**
* 航线文件上传请求 DTO
*
* @author tuoheng
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AirlineUploadRequest {
/**
* 航线文件KML/KMZ
*/
private MultipartFile file;
/**
* 航线名称
*/
private String name;
/**
* 描述
*/
private String description;
}

View File

@ -0,0 +1,45 @@
package com.tuoheng.airport.airline.application.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* 航线验证结果响应 DTO
*
* @author tuoheng
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AirlineValidationResultResponse {
/**
* 是否验证通过
*/
private Boolean valid;
/**
* 错误信息列表
*/
private List<String> errors;
/**
* 警告信息列表
*/
private List<String> warnings;
/**
* 是否穿越禁飞区
*/
private Boolean crossesNoFlyZone;
/**
* 是否有空域冲突
*/
private Boolean hasAirspaceConflict;
}

View File

@ -0,0 +1,35 @@
package com.tuoheng.airport.airline.application.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* 空域冲突检查响应 DTO
*
* @author tuoheng
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AirspaceConflictCheckResponse {
/**
* 是否有冲突
*/
private Boolean hasConflict;
/**
* 冲突的飞行记录ID列表
*/
private List<Long> conflictFlightRecordIds;
/**
* 冲突描述
*/
private String conflictDescription;
}

View File

@ -0,0 +1,48 @@
package com.tuoheng.airport.airline.application.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 紧急航线生成请求 DTO
*
* @author tuoheng
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class EmergencyAirlineRequest {
/**
* 起点纬度
*/
private Double startLatitude;
/**
* 起点经度
*/
private Double startLongitude;
/**
* 终点纬度
*/
private Double endLatitude;
/**
* 终点经度
*/
private Double endLongitude;
/**
* 飞行高度
*/
private Double altitude;
/**
* 飞行速度m/s
*/
private Double speed;
}

View File

@ -0,0 +1,53 @@
package com.tuoheng.airport.airline.application.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 航点请求 DTO
*
* @author tuoheng
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class WaypointRequest {
/**
* 航点索引
*/
private Integer waypointIndex;
/**
* 纬度
*/
private Double latitude;
/**
* 经度
*/
private Double longitude;
/**
* 高度
*/
private Double altitude;
/**
* 速度m/s
*/
private Double speed;
/**
* 动作
*/
private String action;
/**
* 动作参数
*/
private String actionParam;
}

View File

@ -0,0 +1,28 @@
package com.tuoheng.airport.airline.application.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 航点响应 DTO
*
* @author tuoheng
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class WaypointResponse {
private Long id;
private Long airlineId;
private Integer waypointIndex;
private Double latitude;
private Double longitude;
private Double altitude;
private Double speed;
private String action;
private String actionParam;
}

View File

@ -0,0 +1,226 @@
package com.tuoheng.airport.airline.application.service;
import com.tuoheng.airport.airline.application.dto.*;
import java.util.List;
/**
* 航线应用服务接口Application层
* 定义航线相关的用例Use Cases
* 协调领域模型完成航线管理操作
*
* @author tuoheng
*/
public interface AirlineApplicationService {
/**
* 创建航线
* 业务逻辑
* 1. 验证航线名称唯一性
* 2. 验证航点数据合法性
* 3. 创建航线记录
* 4. 保存航点信息
*
* @param request 创建请求
* @return 航线响应
*/
AirlineResponse createAirline(AirlineCreateRequest request);
/**
* 上传航线文件
* 业务逻辑
* 1. 验证文件格式KML/KMZ
* 2. 解析航线文件
* 3. 提取航点信息
* 4. 创建航线记录
* 5. 上传文件到存储服务
*
* @param request 上传请求
* @return 航线响应
*/
AirlineResponse uploadAirlineFile(AirlineUploadRequest request);
/**
* 更新航线信息
* 业务逻辑
* 1. 验证航线存在
* 2. 检查航线状态已审核的航线不能修改
* 3. 更新航线基本信息
*
* @param request 更新请求
* @return 航线响应
*/
AirlineResponse updateAirline(AirlineUpdateRequest request);
/**
* 根据ID查询航线
*
* @param id 航线ID
* @return 航线响应
*/
AirlineResponse getAirlineById(Long id);
/**
* 根据条件查询航线列表
*
* @param request 查询请求
* @return 航线列表
*/
List<AirlineResponse> queryAirlines(AirlineQueryRequest request);
/**
* 删除航线
* 业务逻辑
* 1. 验证航线存在
* 2. 检查航线是否被任务使用
* 3. 逻辑删除航线
* 4. 删除关联的航点
*
* @param id 航线ID
*/
void deleteAirline(Long id);
/**
* 验证航线
* 业务逻辑
* 1. 验证航点数量至少2个
* 2. 验证航点坐标合法性
* 3. 验证飞行高度范围
* 4. 验证飞行速度范围
* 5. 检查空域冲突
* 6. 检查禁飞区
*
* @param id 航线ID
* @return 验证结果
*/
AirlineValidationResultResponse validateAirline(Long id);
/**
* 提交航线审核
* 业务逻辑
* 1. 验证航线合法性
* 2. 更新航线状态为待审核
* 3. 创建审核记录
* 4. 发送审核通知
*
* @param id 航线ID
* @return 航线响应
*/
AirlineResponse submitAirlineForReview(Long id);
/**
* 审核通过航线
* 业务逻辑
* 1. 验证航线状态为待审核
* 2. 更新航线状态为已审核
* 3. 记录审核意见
* 4. 发送审核结果通知
*
* @param id 航线ID
* @param comment 审核意见
* @return 航线响应
*/
AirlineResponse approveAirline(Long id, String comment);
/**
* 审核拒绝航线
* 业务逻辑
* 1. 验证航线状态为待审核
* 2. 更新航线状态为审核拒绝
* 3. 记录拒绝原因
* 4. 发送审核结果通知
*
* @param id 航线ID
* @param reason 拒绝原因
* @return 航线响应
*/
AirlineResponse rejectAirline(Long id, String reason);
/**
* 复制航线
* 业务逻辑
* 1. 验证源航线存在
* 2. 复制航线基本信息
* 3. 复制所有航点
* 4. 新航线状态为草稿
*
* @param id 源航线ID
* @param newName 新航线名称
* @return 新航线响应
*/
AirlineResponse copyAirline(Long id, String newName);
/**
* 查询航线的航点列表
*
* @param airlineId 航线ID
* @return 航点列表
*/
List<WaypointResponse> getAirlineWaypoints(Long airlineId);
/**
* 计算航线飞行时长
* 业务逻辑
* 1. 获取航线的所有航点
* 2. 计算航点间的距离
* 3. 根据飞行速度计算时长
* 4. 加上起降时间
*
* @param airlineId 航线ID
* @param speed 飞行速度m/s
* @return 飞行时长
*/
Long calculateFlightDuration(Long airlineId, Double speed);
/**
* 生成紧急航线
* 业务逻辑
* 1. 验证起点和终点坐标
* 2. 使用最短路径算法生成航线
* 3. 避开禁飞区和障碍物
* 4. 创建临时航线记录
*
* @param request 紧急航线请求
* @return 航线响应
*/
AirlineResponse generateEmergencyAirline(EmergencyAirlineRequest request);
/**
* 下载航线文件
* 业务逻辑
* 1. 验证航线存在
* 2. 生成KML/KMZ文件
* 3. 返回文件流
*
* @param id 航线ID
* @param format 文件格式kml/kmz
*/
void downloadAirlineFile(Long id, String format);
/**
* 优化航线
* 业务逻辑
* 1. 分析航线路径
* 2. 优化航点顺序
* 3. 减少不必要的航点
* 4. 平滑飞行路径
*
* @param id 航线ID
* @return 优化后的航线
*/
AirlineResponse optimizeAirline(Long id);
/**
* 检查航线空域冲突
* 业务逻辑
* 1. 查询指定时间段内的其他飞行计划
* 2. 检查航线是否有重叠
* 3. 返回冲突信息
*
* @param airlineId 航线ID
* @param startTime 开始时间
* @param endTime 结束时间
* @return 冲突检查结果
*/
AirspaceConflictCheckResponse checkAirspaceConflict(Long airlineId, String startTime, String endTime);
}

View File

@ -0,0 +1,96 @@
package com.tuoheng.airport.airline.domain.model;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
/**
* 航线领域模型
*
* @author tuoheng
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Airline {
private Long id;
private String airlineCode;
private String airlineName;
private String description;
private AirlineStatus status;
private String fileUrl;
private Double totalDistance;
private Long estimatedDuration;
private Long tenantId;
private Long creatorId;
private Long reviewerId;
private String reviewComment;
private LocalDateTime reviewTime;
private LocalDateTime createTime;
private LocalDateTime updateTime;
private Boolean deleted;
// ==================== 业务方法 ====================
public void submitForReview() {
if (this.status != AirlineStatus.DRAFT) {
throw new IllegalStateException("只有草稿状态的航线可以提交审核");
}
this.status = AirlineStatus.PENDING_REVIEW;
this.updateTime = LocalDateTime.now();
}
public void approve(Long reviewerId, String comment) {
if (this.status != AirlineStatus.PENDING_REVIEW) {
throw new IllegalStateException("只有待审核状态的航线可以审核");
}
this.status = AirlineStatus.APPROVED;
this.reviewerId = reviewerId;
this.reviewComment = comment;
this.reviewTime = LocalDateTime.now();
this.updateTime = LocalDateTime.now();
}
public void reject(Long reviewerId, String reason) {
if (this.status != AirlineStatus.PENDING_REVIEW) {
throw new IllegalStateException("只有待审核状态的航线可以拒绝");
}
this.status = AirlineStatus.REJECTED;
this.reviewerId = reviewerId;
this.reviewComment = reason;
this.reviewTime = LocalDateTime.now();
this.updateTime = LocalDateTime.now();
}
public boolean isApproved() {
return this.status == AirlineStatus.APPROVED;
}
public boolean canModify() {
return this.status == AirlineStatus.DRAFT || this.status == AirlineStatus.REJECTED;
}
public void delete() {
this.deleted = true;
this.updateTime = LocalDateTime.now();
}
public static Airline create(String airlineCode, String airlineName, Long tenantId, Long creatorId) {
LocalDateTime now = LocalDateTime.now();
return Airline.builder()
.airlineCode(airlineCode)
.airlineName(airlineName)
.status(AirlineStatus.DRAFT)
.tenantId(tenantId)
.creatorId(creatorId)
.createTime(now)
.updateTime(now)
.deleted(false)
.build();
}
}

View File

@ -0,0 +1,24 @@
package com.tuoheng.airport.airline.domain.model;
/**
* 航线状态枚举
*
* @author tuoheng
*/
public enum AirlineStatus {
DRAFT("草稿"),
PENDING_REVIEW("待审核"),
APPROVED("已审核"),
REJECTED("已拒绝");
private final String description;
AirlineStatus(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
}

View File

@ -0,0 +1,53 @@
package com.tuoheng.airport.airline.domain.model;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* 航线验证结果领域模型
*
* @author tuoheng
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AirlineValidationResult {
/**
* 是否验证通过
*/
private Boolean valid;
/**
* 错误信息列表
*/
private List<String> errors;
/**
* 警告信息列表
*/
private List<String> warnings;
/**
* 是否穿越禁飞区
*/
private Boolean crossesNoFlyZone;
/**
* 是否有空域冲突
*/
private Boolean hasAirspaceConflict;
public boolean hasErrors() {
return errors != null && !errors.isEmpty();
}
public boolean hasWarnings() {
return warnings != null && !warnings.isEmpty();
}
}

View File

@ -0,0 +1,56 @@
package com.tuoheng.airport.airline.domain.model;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 航点领域模型
*
* @author tuoheng
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Waypoint {
private Long id;
private Long airlineId;
private Integer waypointIndex;
private Double latitude;
private Double longitude;
private Double altitude;
private Double speed;
private String action;
private String actionParam;
// ==================== 业务方法 ====================
public void validateCoordinates() {
if (latitude == null || latitude < -90 || latitude > 90) {
throw new IllegalArgumentException("纬度必须在-90到90之间");
}
if (longitude == null || longitude < -180 || longitude > 180) {
throw new IllegalArgumentException("经度必须在-180到180之间");
}
if (altitude == null || altitude < 0 || altitude > 500) {
throw new IllegalArgumentException("高度必须在0到500米之间");
}
}
public static Waypoint create(Long airlineId, Integer waypointIndex, Double latitude,
Double longitude, Double altitude, Double speed) {
Waypoint waypoint = Waypoint.builder()
.airlineId(airlineId)
.waypointIndex(waypointIndex)
.latitude(latitude)
.longitude(longitude)
.altitude(altitude)
.speed(speed)
.build();
waypoint.validateCoordinates();
return waypoint;
}
}

View File

@ -0,0 +1,32 @@
package com.tuoheng.airport.airline.domain.repository;
import com.tuoheng.airport.airline.domain.model.Airline;
import java.util.List;
import java.util.Optional;
/**
* 航线仓储接口
*
* @author tuoheng
*/
public interface AirlineRepository {
Airline save(Airline airline);
Optional<Airline> findById(Long id);
Optional<Airline> findByAirlineCode(String airlineCode);
List<Airline> findByStatus(String status);
List<Airline> findByTenantId(Long tenantId);
List<Airline> findByCreatorId(Long creatorId);
void delete(Long id);
boolean existsByAirlineName(String airlineName, Long tenantId);
long countByStatus(String status);
}

View File

@ -0,0 +1,26 @@
package com.tuoheng.airport.airline.domain.repository;
import com.tuoheng.airport.airline.domain.model.Waypoint;
import java.util.List;
import java.util.Optional;
/**
* 航点仓储接口
*
* @author tuoheng
*/
public interface WaypointRepository {
Waypoint save(Waypoint waypoint);
Optional<Waypoint> findById(Long id);
List<Waypoint> findByAirlineId(Long airlineId);
void delete(Long id);
void deleteByAirlineId(Long airlineId);
long countByAirlineId(Long airlineId);
}

View File

@ -0,0 +1,305 @@
package com.tuoheng.airport.airline.domain.service;
import com.tuoheng.airport.airline.domain.model.Airline;
import com.tuoheng.airport.airline.domain.model.Waypoint;
import com.tuoheng.airport.airline.domain.model.AirlineValidationResult;
import java.util.List;
/**
* 航线领域服务接口Domain层
* 封装航线相关的业务逻辑和业务规则
*
* Domain Service 的职责
* 1. 封装航线管理的核心业务规则
* 2. 协调航线和航点的关系
* 3. 保证航线数据的一致性
* 4. 不包含事务管理事务由 Application 层管理
*
* @author tuoheng
*/
public interface AirlineDomainService {
/**
* 创建航线
* 业务规则
* 1. 航线名称不能重复同一租户下
* 2. 航点数量至少2个
* 3. 航点顺序必须连续
* 4. 新航线默认为草稿状态
*
* @param airline 航线领域模型
* @return 创建后的航线
*/
Airline createAirline(Airline airline);
/**
* 更新航线信息
* 业务规则
* 1. 已审核的航线不能修改
* 2. 不能修改航线编码
* 3. 修改后需要重新审核
*
* @param airline 航线领域模型
* @return 更新后的航线
*/
Airline updateAirline(Airline airline);
/**
* 删除航线
* 业务规则
* 1. 检查航线是否被任务使用
* 2. 被使用的航线不能删除
* 3. 逻辑删除航线
* 4. 删除关联的航点
*
* @param airlineId 航线ID
*/
void deleteAirline(Long airlineId);
/**
* 验证航线合法性
* 业务规则
* 1. 航点数量至少2个
* 2. 航点坐标必须合法经纬度范围
* 3. 飞行高度在允许范围内0-500米
* 4. 飞行速度在允许范围内1-20m/s
* 5. 不能穿越禁飞区
* 6. 检查空域冲突
*
* @param airlineId 航线ID
* @return 验证结果
*/
AirlineValidationResult validateAirline(Long airlineId);
/**
* 提交航线审核
* 业务规则
* 1. 航线必须通过验证
* 2. 更新航线状态为待审核
* 3. 记录提交时间和提交人
*
* @param airlineId 航线ID
* @return 更新后的航线
*/
Airline submitForReview(Long airlineId);
/**
* 审核通过航线
* 业务规则
* 1. 航线状态必须为待审核
* 2. 更新航线状态为已审核
* 3. 记录审核时间和审核人
*
* @param airlineId 航线ID
* @param reviewerId 审核人ID
* @param comment 审核意见
* @return 更新后的航线
*/
Airline approveAirline(Long airlineId, Long reviewerId, String comment);
/**
* 审核拒绝航线
* 业务规则
* 1. 航线状态必须为待审核
* 2. 更新航线状态为审核拒绝
* 3. 记录拒绝原因
*
* @param airlineId 航线ID
* @param reviewerId 审核人ID
* @param reason 拒绝原因
* @return 更新后的航线
*/
Airline rejectAirline(Long airlineId, Long reviewerId, String reason);
/**
* 复制航线
* 业务规则
* 1. 复制航线基本信息
* 2. 复制所有航点
* 3. 新航线状态为草稿
* 4. 新航线名称不能重复
*
* @param sourceAirlineId 源航线ID
* @param newName 新航线名称
* @return 新航线
*/
Airline copyAirline(Long sourceAirlineId, String newName);
/**
* 查询航线详情
*
* @param airlineId 航线ID
* @return 航线领域模型
*/
Airline getAirlineById(Long airlineId);
/**
* 查询航线的航点列表
*
* @param airlineId 航线ID
* @return 航点列表按顺序排序
*/
List<Waypoint> getAirlineWaypoints(Long airlineId);
/**
* 添加航点到航线
* 业务规则
* 1. 航线必须是草稿状态
* 2. 航点顺序自动递增
* 3. 验证航点坐标合法性
*
* @param airlineId 航线ID
* @param waypoint 航点
* @return 添加后的航点
*/
Waypoint addWaypoint(Long airlineId, Waypoint waypoint);
/**
* 更新航点信息
* 业务规则
* 1. 航线必须是草稿状态
* 2. 验证航点坐标合法性
*
* @param waypoint 航点
* @return 更新后的航点
*/
Waypoint updateWaypoint(Waypoint waypoint);
/**
* 删除航点
* 业务规则
* 1. 航线必须是草稿状态
* 2. 删除后重新排序剩余航点
* 3. 航点数量不能少于2个
*
* @param waypointId 航点ID
*/
void deleteWaypoint(Long waypointId);
/**
* 计算航线总距离
* 根据航点坐标计算航线的总飞行距离
*
* @param airlineId 航线ID
* @return 总距离
*/
Double calculateTotalDistance(Long airlineId);
/**
* 计算航线飞行时长
* 根据航线距离和飞行速度计算预计飞行时长
*
* @param airlineId 航线ID
* @param speed 飞行速度m/s
* @return 飞行时长
*/
Long calculateFlightDuration(Long airlineId, Double speed);
/**
* 生成紧急航线
* 业务规则
* 1. 使用Dijkstra最短路径算法
* 2. 避开禁飞区
* 3. 避开障碍物
* 4. 生成临时航线不保存到数据库
*
* @param startLat 起点纬度
* @param startLng 起点经度
* @param endLat 终点纬度
* @param endLng 终点经度
* @param altitude 飞行高度
* @return 紧急航线
*/
Airline generateEmergencyAirline(Double startLat, Double startLng, Double endLat, Double endLng, Double altitude);
/**
* 优化航线
* 业务规则
* 1. 移除冗余航点
* 2. 平滑飞行路径
* 3. 优化转弯角度
* 4. 保持航线总体方向不变
*
* @param airlineId 航线ID
* @return 优化后的航线
*/
Airline optimizeAirline(Long airlineId);
/**
* 检查航线是否穿越禁飞区
* 业务规则
* 1. 查询所有禁飞区
* 2. 检查航线路径是否与禁飞区相交
*
* @param airlineId 航线ID
* @return 是否穿越禁飞区
*/
boolean crossesNoFlyZone(Long airlineId);
/**
* 检查航线空域冲突
* 业务规则
* 1. 查询指定时间段内的其他飞行计划
* 2. 检查航线是否有重叠
* 3. 检查飞行高度是否有冲突
*
* @param airlineId 航线ID
* @param startTime 开始时间
* @param endTime 结束时间
* @return 是否有冲突
*/
boolean hasAirspaceConflict(Long airlineId, String startTime, String endTime);
/**
* 检查航线名称是否存在
*
* @param name 航线名称
* @param tenantId 租户ID
* @return 是否存在
*/
boolean isAirlineNameExists(String name, Long tenantId);
/**
* 检查航线是否被使用
* 业务规则
* 1. 检查是否有任务使用该航线
* 2. 被使用的航线不能删除
*
* @param airlineId 航线ID
* @return 是否被使用
*/
boolean isAirlineInUse(Long airlineId);
/**
* 解析航线文件
* 从KML/KMZ文件中解析航点信息
*
* @param fileContent 文件内容
* @param fileType 文件类型kml/kmz
* @return 航点列表
*/
List<Waypoint> parseAirlineFile(String fileContent, String fileType);
/**
* 生成航线文件
* 将航线和航点信息导出为KML/KMZ文件
*
* @param airlineId 航线ID
* @param fileType 文件类型kml/kmz
* @return 文件内容
*/
String generateAirlineFile(Long airlineId, String fileType);
/**
* 计算两点之间的距离
* 使用Haversine公式计算地球表面两点间的距离
*
* @param lat1 点1纬度
* @param lng1 点1经度
* @param lat2 点2纬度
* @param lng2 点2经度
* @return 距离
*/
Double calculateDistance(Double lat1, Double lng1, Double lat2, Double lng2);
}

View File

@ -0,0 +1,265 @@
package com.tuoheng.airport.airline.presentation.controller;
import com.tuoheng.airport.airline.application.dto.*;
import com.tuoheng.airport.airline.application.service.AirlineApplicationService;
import com.tuoheng.airport.airline.presentation.converter.AirlineVoConverter;
import com.tuoheng.airport.airline.presentation.vo.*;
import com.tuoheng.common.response.Result;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.validation.Valid;
import java.util.List;
/**
* 航线管理控制器Presentation层
* 提供航线管理的 REST API 接口
* 负责航线航点空域的管理
*
* 职责
* 1. 接收前端的 VO 对象
* 2. VO 转换为 DTO 传递给 Application
* 3. Application 层返回的 DTO 转换为 VO 返回给前端
* 4. 不包含任何业务逻辑
*
* @author tuoheng
*/
@Slf4j
@RestController
@RequestMapping("/api/v1/airlines")
@RequiredArgsConstructor
@Validated
@Tag(name = "航线管理", description = "航线管理相关接口")
public class AirlineController {
private final AirlineApplicationService airlineApplicationService;
/**
* 创建航线
* POST /api/v1/airlines
*/
@PostMapping
@Operation(summary = "创建航线", description = "创建新的飞行航线")
public Result<AirlineVO> createAirline(@Valid @RequestBody AirlineCreateVO vo) {
log.info("接收到创建航线请求: {}", vo);
AirlineCreateRequest request = AirlineVoConverter.toCreateRequest(vo);
AirlineResponse response = airlineApplicationService.createAirline(request);
AirlineVO result = AirlineVoConverter.toVO(response);
return Result.success(result);
}
/**
* 上传航线文件
* POST /api/v1/airlines/upload
*/
@PostMapping("/upload")
@Operation(summary = "上传航线文件", description = "上传KML/KMZ格式的航线文件")
public Result<AirlineVO> uploadAirlineFile(
@Parameter(description = "航线文件") @RequestParam("file") MultipartFile file,
@Parameter(description = "航线名称") @RequestParam String name,
@Parameter(description = "航线描述") @RequestParam(required = false) String description) {
log.info("接收到上传航线文件请求,文件名: {}, 航线名称: {}", file.getOriginalFilename(), name);
AirlineUploadRequest request = AirlineUploadRequest.builder()
.file(file)
.name(name)
.description(description)
.build();
AirlineResponse response = airlineApplicationService.uploadAirlineFile(request);
AirlineVO result = AirlineVoConverter.toVO(response);
return Result.success(result);
}
/**
* 更新航线信息
* PUT /api/v1/airlines/{id}
*/
@PutMapping("/{id}")
@Operation(summary = "更新航线信息", description = "更新指定航线的基本信息")
public Result<AirlineVO> updateAirline(
@Parameter(description = "航线ID") @PathVariable Long id,
@Valid @RequestBody AirlineUpdateVO vo) {
log.info("接收到更新航线请求航线ID: {}, 请求参数: {}", id, vo);
vo.setId(id);
AirlineUpdateRequest request = AirlineVoConverter.toUpdateRequest(vo);
AirlineResponse response = airlineApplicationService.updateAirline(request);
AirlineVO result = AirlineVoConverter.toVO(response);
return Result.success(result);
}
/**
* 查询航线详情
* GET /api/v1/airlines/{id}
*/
@GetMapping("/{id}")
@Operation(summary = "查询航线详情", description = "根据航线ID查询航线详细信息")
public Result<AirlineVO> getAirlineById(
@Parameter(description = "航线ID") @PathVariable Long id) {
log.info("接收到查询航线请求航线ID: {}", id);
AirlineResponse response = airlineApplicationService.getAirlineById(id);
AirlineVO result = AirlineVoConverter.toVO(response);
return Result.success(result);
}
/**
* 查询航线列表
* GET /api/v1/airlines
*/
@GetMapping
@Operation(summary = "查询航线列表", description = "根据条件查询航线列表")
public Result<List<AirlineVO>> queryAirlines(AirlineQueryVO vo) {
log.info("接收到查询航线列表请求,查询条件: {}", vo);
AirlineQueryRequest request = AirlineVoConverter.toQueryRequest(vo);
List<AirlineResponse> responses = airlineApplicationService.queryAirlines(request);
List<AirlineVO> results = AirlineVoConverter.toVOList(responses);
return Result.success(results);
}
/**
* 删除航线
* DELETE /api/v1/airlines/{id}
*/
@DeleteMapping("/{id}")
@Operation(summary = "删除航线", description = "删除指定的航线(逻辑删除)")
public Result<Void> deleteAirline(
@Parameter(description = "航线ID") @PathVariable Long id) {
log.info("接收到删除航线请求航线ID: {}", id);
airlineApplicationService.deleteAirline(id);
return Result.success();
}
/**
* 验证航线
* POST /api/v1/airlines/{id}/validate
*/
@PostMapping("/{id}/validate")
@Operation(summary = "验证航线", description = "验证航线的合法性和安全性")
public Result<AirlineValidationResultVO> validateAirline(
@Parameter(description = "航线ID") @PathVariable Long id) {
log.info("接收到验证航线请求航线ID: {}", id);
AirlineValidationResultResponse response = airlineApplicationService.validateAirline(id);
AirlineValidationResultVO result = AirlineVoConverter.toValidationResultVO(response);
return Result.success(result);
}
/**
* 提交航线审核
* POST /api/v1/airlines/{id}/submit-review
*/
@PostMapping("/{id}/submit-review")
@Operation(summary = "提交航线审核", description = "提交航线进行审核")
public Result<AirlineVO> submitAirlineForReview(
@Parameter(description = "航线ID") @PathVariable Long id) {
log.info("接收到提交航线审核请求航线ID: {}", id);
AirlineResponse response = airlineApplicationService.submitAirlineForReview(id);
AirlineVO result = AirlineVoConverter.toVO(response);
return Result.success(result);
}
/**
* 审核通过航线
* POST /api/v1/airlines/{id}/approve
*/
@PostMapping("/{id}/approve")
@Operation(summary = "审核通过航线", description = "审核通过指定的航线")
public Result<AirlineVO> approveAirline(
@Parameter(description = "航线ID") @PathVariable Long id,
@RequestParam(required = false) String comment) {
log.info("接收到审核通过航线请求航线ID: {}, 审核意见: {}", id, comment);
AirlineResponse response = airlineApplicationService.approveAirline(id, comment);
AirlineVO result = AirlineVoConverter.toVO(response);
return Result.success(result);
}
/**
* 审核拒绝航线
* POST /api/v1/airlines/{id}/reject
*/
@PostMapping("/{id}/reject")
@Operation(summary = "审核拒绝航线", description = "审核拒绝指定的航线")
public Result<AirlineVO> rejectAirline(
@Parameter(description = "航线ID") @PathVariable Long id,
@RequestParam String reason) {
log.info("接收到审核拒绝航线请求航线ID: {}, 拒绝原因: {}", id, reason);
AirlineResponse response = airlineApplicationService.rejectAirline(id, reason);
AirlineVO result = AirlineVoConverter.toVO(response);
return Result.success(result);
}
/**
* 复制航线
* POST /api/v1/airlines/{id}/copy
*/
@PostMapping("/{id}/copy")
@Operation(summary = "复制航线", description = "复制现有航线创建新航线")
public Result<AirlineVO> copyAirline(
@Parameter(description = "航线ID") @PathVariable Long id,
@RequestParam String newName) {
log.info("接收到复制航线请求航线ID: {}, 新航线名称: {}", id, newName);
AirlineResponse response = airlineApplicationService.copyAirline(id, newName);
AirlineVO result = AirlineVoConverter.toVO(response);
return Result.success(result);
}
/**
* 查询航线的航点列表
* GET /api/v1/airlines/{id}/waypoints
*/
@GetMapping("/{id}/waypoints")
@Operation(summary = "查询航线航点", description = "查询指定航线的所有航点")
public Result<List<WaypointVO>> getAirlineWaypoints(
@Parameter(description = "航线ID") @PathVariable Long id) {
log.info("接收到查询航线航点请求航线ID: {}", id);
List<WaypointResponse> responses = airlineApplicationService.getAirlineWaypoints(id);
List<WaypointVO> results = AirlineVoConverter.toWaypointVOList(responses);
return Result.success(results);
}
/**
* 计算航线飞行时长
* GET /api/v1/airlines/{id}/duration
*/
@GetMapping("/{id}/duration")
@Operation(summary = "计算飞行时长", description = "计算航线的预计飞行时长")
public Result<Long> calculateFlightDuration(
@Parameter(description = "航线ID") @PathVariable Long id,
@Parameter(description = "飞行速度(m/s)") @RequestParam(defaultValue = "10") Double speed) {
log.info("接收到计算飞行时长请求航线ID: {}, 飞行速度: {}m/s", id, speed);
Long duration = airlineApplicationService.calculateFlightDuration(id, speed);
return Result.success(duration);
}
/**
* 生成紧急航线
* POST /api/v1/airlines/emergency
*/
@PostMapping("/emergency")
@Operation(summary = "生成紧急航线", description = "根据起点和终点动态生成紧急航线")
public Result<AirlineVO> generateEmergencyAirline(@Valid @RequestBody EmergencyAirlineRequestVO vo) {
log.info("接收到生成紧急航线请求: {}", vo);
EmergencyAirlineRequest request = AirlineVoConverter.toEmergencyRequest(vo);
AirlineResponse response = airlineApplicationService.generateEmergencyAirline(request);
AirlineVO result = AirlineVoConverter.toVO(response);
return Result.success(result);
}
/**
* 下载航线文件
* GET /api/v1/airlines/{id}/download
*/
@GetMapping("/{id}/download")
@Operation(summary = "下载航线文件", description = "下载航线的KML/KMZ文件")
public void downloadAirlineFile(
@Parameter(description = "航线ID") @PathVariable Long id,
@Parameter(description = "文件格式") @RequestParam(defaultValue = "kml") String format) {
log.info("接收到下载航线文件请求航线ID: {}, 格式: {}", id, format);
airlineApplicationService.downloadAirlineFile(id, format);
}
}

View File

@ -0,0 +1,57 @@
package com.tuoheng.airport.airline.presentation.converter;
import com.tuoheng.airport.airline.application.dto.*;
import com.tuoheng.airport.airline.presentation.vo.*;
import java.util.List;
import java.util.stream.Collectors;
public class AirlineVoConverter {
public static AirlineCreateRequest toCreateRequest(AirlineCreateVO vo) {
// TODO: 实现转换逻辑
return new AirlineCreateRequest();
}
public static AirlineUpdateRequest toUpdateRequest(AirlineUpdateVO vo) {
// TODO: 实现转换逻辑
return new AirlineUpdateRequest();
}
public static AirlineQueryRequest toQueryRequest(AirlineQueryVO vo) {
// TODO: 实现转换逻辑
return new AirlineQueryRequest();
}
public static AirlineVO toVO(AirlineResponse response) {
// TODO: 实现转换逻辑
return new AirlineVO();
}
public static List<AirlineVO> toVOList(List<AirlineResponse> responses) {
return responses.stream().map(AirlineVoConverter::toVO).collect(Collectors.toList());
}
public static AirlineStatisticsVO toStatisticsVO(AirlineStatisticsResponse response) {
// TODO: 实现转换逻辑
return new AirlineStatisticsVO();
}
public static WaypointVO toWaypointVO(WaypointResponse response) {
// TODO: 实现转换逻辑
return new WaypointVO();
}
public static List<WaypointVO> toWaypointVOList(List<WaypointResponse> responses) {
return responses.stream().map(AirlineVoConverter::toWaypointVO).collect(Collectors.toList());
}
public static EmergencyAirlineRequest toEmergencyRequest(EmergencyAirlineRequestVO vo) {
// TODO: 实现转换逻辑
return new EmergencyAirlineRequest();
}
public static AirlineValidationResultVO toValidationResultVO(AirlineValidationResultResponse response) {
// TODO: 实现转换逻辑
return new AirlineValidationResultVO();
}
}

View File

@ -0,0 +1,45 @@
package com.tuoheng.airport.airline.presentation.vo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.util.List;
/**
* 航线创建 VO
*
* @author tuoheng
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AirlineCreateVO {
/**
* 航线编码
*/
@NotBlank(message = "航线编码不能为空")
private String airlineCode;
/**
* 航线名称
*/
@NotBlank(message = "航线名称不能为空")
private String airlineName;
/**
* 描述
*/
private String description;
/**
* 航点列表
*/
@NotNull(message = "航点列表不能为空")
private List<WaypointVO> waypoints;
}

View File

@ -0,0 +1,33 @@
package com.tuoheng.airport.airline.presentation.vo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 航线查询 VO
*
* @author tuoheng
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AirlineQueryVO {
/**
* 航线名称模糊查询
*/
private String airlineName;
/**
* 航线状态
*/
private String status;
/**
* 创建人ID
*/
private Long creatorId;
}

View File

@ -0,0 +1,8 @@
package com.tuoheng.airport.airline.presentation.vo;
import lombok.Data;
@Data
public class AirlineStatisticsVO {
// TODO: 添加字段
}

View File

@ -0,0 +1,36 @@
package com.tuoheng.airport.airline.presentation.vo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotBlank;
/**
* 航线更新 VO
*
* @author tuoheng
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AirlineUpdateVO {
/**
* 航线ID
*/
private Long id;
/**
* 航线名称
*/
@NotBlank(message = "航线名称不能为空")
private String airlineName;
/**
* 描述
*/
private String description;
}

View File

@ -0,0 +1,36 @@
package com.tuoheng.airport.airline.presentation.vo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
/**
* 航线展示 VO
*
* @author tuoheng
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AirlineVO {
private Long id;
private String airlineCode;
private String airlineName;
private String description;
private String status;
private String fileUrl;
private Double totalDistance;
private Long estimatedDuration;
private Long tenantId;
private Long creatorId;
private Long reviewerId;
private String reviewComment;
private LocalDateTime reviewTime;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}

View File

@ -0,0 +1,45 @@
package com.tuoheng.airport.airline.presentation.vo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* 航线验证结果 VO
*
* @author tuoheng
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AirlineValidationResultVO {
/**
* 是否验证通过
*/
private Boolean valid;
/**
* 错误信息列表
*/
private List<String> errors;
/**
* 警告信息列表
*/
private List<String> warnings;
/**
* 是否穿越禁飞区
*/
private Boolean crossesNoFlyZone;
/**
* 是否有空域冲突
*/
private Boolean hasAirspaceConflict;
}

View File

@ -0,0 +1,55 @@
package com.tuoheng.airport.airline.presentation.vo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotNull;
/**
* 紧急航线生成请求 VO
*
* @author tuoheng
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class EmergencyAirlineRequestVO {
/**
* 起点纬度
*/
@NotNull(message = "起点纬度不能为空")
private Double startLatitude;
/**
* 起点经度
*/
@NotNull(message = "起点经度不能为空")
private Double startLongitude;
/**
* 终点纬度
*/
@NotNull(message = "终点纬度不能为空")
private Double endLatitude;
/**
* 终点经度
*/
@NotNull(message = "终点经度不能为空")
private Double endLongitude;
/**
* 飞行高度
*/
@NotNull(message = "飞行高度不能为空")
private Double altitude;
/**
* 飞行速度m/s
*/
private Double speed;
}

View File

@ -0,0 +1,62 @@
package com.tuoheng.airport.airline.presentation.vo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotNull;
/**
* 航点 VO
*
* @author tuoheng
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class WaypointVO {
private Long id;
private Long airlineId;
/**
* 航点索引
*/
@NotNull(message = "航点索引不能为空")
private Integer waypointIndex;
/**
* 纬度
*/
@NotNull(message = "纬度不能为空")
private Double latitude;
/**
* 经度
*/
@NotNull(message = "经度不能为空")
private Double longitude;
/**
* 高度
*/
@NotNull(message = "高度不能为空")
private Double altitude;
/**
* 速度m/s
*/
private Double speed;
/**
* 动作
*/
private String action;
/**
* 动作参数
*/
private String actionParam;
}

View File

@ -0,0 +1,8 @@
package com.tuoheng.airport.approval.application.dto;
import lombok.Data;
@Data
public class ApprovalCreateRequest {
// TODO: 添加字段
}

View File

@ -0,0 +1,8 @@
package com.tuoheng.airport.approval.application.dto;
import lombok.Data;
@Data
public class ApprovalDecisionRequest {
private String comment;
}

View File

@ -0,0 +1,8 @@
package com.tuoheng.airport.approval.application.dto;
import lombok.Data;
@Data
public class ApprovalHistoryResponse {
// TODO: 添加字段
}

View File

@ -0,0 +1,8 @@
package com.tuoheng.airport.approval.application.dto;
import lombok.Data;
@Data
public class ApprovalQueryRequest {
// TODO: 添加字段
}

View File

@ -0,0 +1,8 @@
package com.tuoheng.airport.approval.application.dto;
import lombok.Data;
@Data
public class ApprovalResponse {
// TODO: 添加字段
}

View File

@ -0,0 +1,8 @@
package com.tuoheng.airport.approval.application.dto;
import lombok.Data;
@Data
public class ApprovalStatisticsResponse {
// TODO: 添加字段
}

View File

@ -0,0 +1,10 @@
package com.tuoheng.airport.approval.application.dto;
import lombok.Data;
import java.util.List;
@Data
public class BatchApprovalRequest {
private List<Long> approvalIds;
private String comment;
}

View File

@ -0,0 +1,9 @@
package com.tuoheng.airport.approval.application.dto;
import lombok.Data;
@Data
public class BatchApprovalResultResponse {
private Integer successCount;
private Integer failureCount;
}

View File

@ -0,0 +1,226 @@
package com.tuoheng.airport.approval.application.service;
import com.tuoheng.airport.approval.application.dto.*;
import java.util.List;
/**
* 审批应用服务接口Application层
* 定义审批流程相关的用例Use Cases
* 协调领域模型完成审批流程管理操作
*
* @author tuoheng
*/
public interface ApprovalApplicationService {
/**
* 创建审批申请
* 业务逻辑
* 1. 验证申请数据完整性
* 2. 创建审批记录
* 3. 初始化审批状态为草稿
* 4. 关联业务对象航线设备等
*
* @param request 创建请求
* @return 审批响应
*/
ApprovalResponse createApproval(ApprovalCreateRequest request);
/**
* 提交审批申请
* 业务逻辑
* 1. 验证审批状态为草稿
* 2. 验证申请数据完整性
* 3. 确定审批人根据审批类型和规则
* 4. 更新审批状态为待审批
* 5. 发送审批通知
*
* @param id 审批ID
* @return 审批响应
*/
ApprovalResponse submitApproval(Long id);
/**
* 审批通过
* 业务逻辑
* 1. 验证审批状态为待审批
* 2. 验证当前用户是审批人
* 3. 记录审批意见
* 4. 更新审批状态为已通过
* 5. 执行业务回调如航线审核通过
* 6. 发送审批结果通知
*
* @param id 审批ID
* @param request 审批决策请求
* @return 审批响应
*/
ApprovalResponse approveApproval(Long id, ApprovalDecisionRequest request);
/**
* 审批拒绝
* 业务逻辑
* 1. 验证审批状态为待审批
* 2. 验证当前用户是审批人
* 3. 记录拒绝原因
* 4. 更新审批状态为已拒绝
* 5. 执行业务回调如航线审核拒绝
* 6. 发送审批结果通知
*
* @param id 审批ID
* @param request 审批决策请求
* @return 审批响应
*/
ApprovalResponse rejectApproval(Long id, ApprovalDecisionRequest request);
/**
* 撤回审批申请
* 业务逻辑
* 1. 验证审批状态为待审批
* 2. 验证当前用户是申请人
* 3. 更新审批状态为已撤回
* 4. 记录撤回原因
* 5. 发送撤回通知
*
* @param id 审批ID
* @param reason 撤回原因
* @return 审批响应
*/
ApprovalResponse withdrawApproval(Long id, String reason);
/**
* 转审
* 业务逻辑
* 1. 验证审批状态为待审批
* 2. 验证当前用户是审批人
* 3. 验证目标审批人有效
* 4. 更新审批人
* 5. 记录转审原因
* 6. 发送转审通知
*
* @param id 审批ID
* @param targetApproverId 目标审批人ID
* @param reason 转审原因
* @return 审批响应
*/
ApprovalResponse transferApproval(Long id, Long targetApproverId, String reason);
/**
* 查询审批详情
*
* @param id 审批ID
* @return 审批响应
*/
ApprovalResponse getApprovalById(Long id);
/**
* 根据条件查询审批列表
*
* @param request 查询请求
* @return 审批列表
*/
List<ApprovalResponse> queryApprovals(ApprovalQueryRequest request);
/**
* 查询我的待审批列表
* 业务逻辑
* 1. 获取当前用户ID
* 2. 查询审批人为当前用户且状态为待审批的记录
* 3. 按提交时间倒序排序
*
* @return 审批列表
*/
List<ApprovalResponse> getMyPendingApprovals();
/**
* 查询我的申请列表
* 业务逻辑
* 1. 获取当前用户ID
* 2. 查询申请人为当前用户的记录
* 3. 可按状态筛选
* 4. 按提交时间倒序排序
*
* @param status 审批状态可选
* @return 审批列表
*/
List<ApprovalResponse> getMyApplications(String status);
/**
* 查询审批历史
* 业务逻辑
* 1. 查询审批的所有流转记录
* 2. 包括提交审批转审撤回等操作
* 3. 按时间顺序排序
*
* @param id 审批ID
* @return 审批历史列表
*/
List<ApprovalHistoryResponse> getApprovalHistory(Long id);
/**
* 查询审批统计信息
* 业务逻辑
* 1. 统计审批总数
* 2. 按状态分组统计待审批已通过已拒绝
* 3. 按审批类型分组统计
* 4. 计算平均审批时长
* 5. 按日期分组统计
*
* @param approvalType 审批类型可选
* @param startDate 开始日期可选
* @param endDate 结束日期可选
* @return 审批统计信息
*/
ApprovalStatisticsResponse getApprovalStatistics(String approvalType, String startDate, String endDate);
/**
* 删除审批申请
* 业务逻辑
* 1. 验证审批状态为草稿
* 2. 验证当前用户是申请人
* 3. 逻辑删除审批记录
*
* @param id 审批ID
*/
void deleteApproval(Long id);
/**
* 批量审批
* 业务逻辑
* 1. 验证所有审批的状态和权限
* 2. 批量更新审批状态
* 3. 批量执行业务回调
* 4. 批量发送通知
* 5. 返回成功和失败的统计
*
* @param request 批量审批请求
* @return 批量审批结果
*/
BatchApprovalResultResponse batchApprove(BatchApprovalRequest request);
/**
* 催办审批
* 业务逻辑
* 1. 验证审批状态为待审批
* 2. 验证当前用户是申请人
* 3. 检查距离上次催办时间至少间隔1小时
* 4. 发送催办通知给审批人
*
* @param id 审批ID
*/
void urgeApproval(Long id);
/**
* 加签增加审批人
* 业务逻辑
* 1. 验证审批状态为待审批
* 2. 验证当前用户是审批人
* 3. 添加新的审批人
* 4. 发送加签通知
*
* @param id 审批ID
* @param additionalApproverId 新增审批人ID
* @param reason 加签原因
* @return 审批响应
*/
ApprovalResponse addApprover(Long id, Long additionalApproverId, String reason);
}

View File

@ -0,0 +1,131 @@
package com.tuoheng.airport.approval.domain.model;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
/**
* 审批领域模型
*
* @author tuoheng
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Approval {
private Long id;
private String approvalCode;
private String approvalType;
private String title;
private String content;
private ApprovalStatus status;
private Long applicantId;
private Long approverId;
private String approvalComment;
private Long bizId;
private String bizType;
private Long tenantId;
private LocalDateTime submitTime;
private LocalDateTime approvalTime;
private LocalDateTime createTime;
private LocalDateTime updateTime;
private Boolean deleted;
// ==================== 业务方法 ====================
public void submit(Long approverId) {
if (this.status != ApprovalStatus.DRAFT) {
throw new IllegalStateException("只有草稿状态的审批可以提交");
}
this.status = ApprovalStatus.PENDING;
this.approverId = approverId;
this.submitTime = LocalDateTime.now();
this.updateTime = LocalDateTime.now();
}
public void approve(Long approverId, String comment) {
if (this.status != ApprovalStatus.PENDING) {
throw new IllegalStateException("只有待审批状态的审批可以通过");
}
if (!this.approverId.equals(approverId)) {
throw new IllegalStateException("只有审批人可以审批");
}
this.status = ApprovalStatus.APPROVED;
this.approvalComment = comment;
this.approvalTime = LocalDateTime.now();
this.updateTime = LocalDateTime.now();
}
public void reject(Long approverId, String reason) {
if (this.status != ApprovalStatus.PENDING) {
throw new IllegalStateException("只有待审批状态的审批可以拒绝");
}
if (!this.approverId.equals(approverId)) {
throw new IllegalStateException("只有审批人可以审批");
}
this.status = ApprovalStatus.REJECTED;
this.approvalComment = reason;
this.approvalTime = LocalDateTime.now();
this.updateTime = LocalDateTime.now();
}
public void withdraw(Long applicantId, String reason) {
if (this.status != ApprovalStatus.PENDING) {
throw new IllegalStateException("只有待审批状态的审批可以撤回");
}
if (!this.applicantId.equals(applicantId)) {
throw new IllegalStateException("只有申请人可以撤回");
}
this.status = ApprovalStatus.WITHDRAWN;
this.approvalComment = reason;
this.updateTime = LocalDateTime.now();
}
public void transfer(Long currentApproverId, Long targetApproverId, String reason) {
if (this.status != ApprovalStatus.PENDING) {
throw new IllegalStateException("只有待审批状态的审批可以转审");
}
if (!this.approverId.equals(currentApproverId)) {
throw new IllegalStateException("只有当前审批人可以转审");
}
this.approverId = targetApproverId;
this.approvalComment = reason;
this.updateTime = LocalDateTime.now();
}
public boolean isPending() {
return this.status == ApprovalStatus.PENDING;
}
public void delete() {
if (this.status != ApprovalStatus.DRAFT) {
throw new IllegalStateException("只有草稿状态的审批可以删除");
}
this.deleted = true;
this.updateTime = LocalDateTime.now();
}
public static Approval create(String approvalCode, String approvalType, String title,
String content, Long applicantId, String bizType, Long bizId, Long tenantId) {
LocalDateTime now = LocalDateTime.now();
return Approval.builder()
.approvalCode(approvalCode)
.approvalType(approvalType)
.title(title)
.content(content)
.status(ApprovalStatus.DRAFT)
.applicantId(applicantId)
.bizType(bizType)
.bizId(bizId)
.tenantId(tenantId)
.createTime(now)
.updateTime(now)
.deleted(false)
.build();
}
}

View File

@ -0,0 +1,40 @@
package com.tuoheng.airport.approval.domain.model;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
/**
* 审批历史领域模型
*
* @author tuoheng
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ApprovalHistory {
private Long id;
private Long approvalId;
private String action;
private Long operatorId;
private String operatorName;
private String comment;
private LocalDateTime operateTime;
public static ApprovalHistory create(Long approvalId, String action, Long operatorId,
String operatorName, String comment) {
return ApprovalHistory.builder()
.approvalId(approvalId)
.action(action)
.operatorId(operatorId)
.operatorName(operatorName)
.comment(comment)
.operateTime(LocalDateTime.now())
.build();
}
}

View File

@ -0,0 +1,68 @@
package com.tuoheng.airport.approval.domain.model;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 审批统计领域模型
*
* @author tuoheng
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ApprovalStatistics {
/**
* 审批总数
*/
private Long totalCount;
/**
* 待审批数量
*/
private Long pendingCount;
/**
* 已通过数量
*/
private Long approvedCount;
/**
* 已拒绝数量
*/
private Long rejectedCount;
/**
* 已撤回数量
*/
private Long withdrawnCount;
/**
* 平均审批时长小时
*/
private Double averageApprovalDuration;
/**
* 通过率
*/
public double getApprovalRate() {
if (totalCount == null || totalCount == 0) {
return 0.0;
}
return (double) approvedCount / totalCount * 100;
}
/**
* 拒绝率
*/
public double getRejectionRate() {
if (totalCount == null || totalCount == 0) {
return 0.0;
}
return (double) rejectedCount / totalCount * 100;
}
}

View File

@ -0,0 +1,25 @@
package com.tuoheng.airport.approval.domain.model;
/**
* 审批状态枚举
*
* @author tuoheng
*/
public enum ApprovalStatus {
DRAFT("草稿"),
PENDING("待审批"),
APPROVED("已通过"),
REJECTED("已拒绝"),
WITHDRAWN("已撤回");
private final String description;
ApprovalStatus(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
}

View File

@ -0,0 +1,19 @@
package com.tuoheng.airport.approval.domain.repository;
import com.tuoheng.airport.approval.domain.model.ApprovalHistory;
import java.util.List;
/**
* 审批历史仓储接口
*
* @author tuoheng
*/
public interface ApprovalHistoryRepository {
ApprovalHistory save(ApprovalHistory approvalHistory);
List<ApprovalHistory> findByApprovalId(Long approvalId);
void deleteByApprovalId(Long approvalId);
}

View File

@ -0,0 +1,36 @@
package com.tuoheng.airport.approval.domain.repository;
import com.tuoheng.airport.approval.domain.model.Approval;
import java.util.List;
import java.util.Optional;
/**
* 审批仓储接口
*
* @author tuoheng
*/
public interface ApprovalRepository {
Approval save(Approval approval);
Optional<Approval> findById(Long id);
Optional<Approval> findByApprovalCode(String approvalCode);
List<Approval> findByApplicantId(Long applicantId);
List<Approval> findByApproverId(Long approverId);
List<Approval> findByStatus(String status);
List<Approval> findByApprovalType(String approvalType);
List<Approval> findByBizTypeAndBizId(String bizType, Long bizId);
void delete(Long id);
long countByStatus(String status);
long countByApprovalType(String approvalType);
}

View File

@ -0,0 +1,296 @@
package com.tuoheng.airport.approval.domain.service;
import com.tuoheng.airport.approval.domain.model.Approval;
import com.tuoheng.airport.approval.domain.model.ApprovalHistory;
import com.tuoheng.airport.approval.domain.model.ApprovalStatistics;
import java.util.List;
/**
* 审批领域服务接口Domain层
* 封装审批流程相关的业务逻辑和业务规则
*
* Domain Service 的职责
* 1. 封装审批流程的核心业务规则
* 2. 协调审批记录和审批历史
* 3. 保证审批数据的一致性
* 4. 不包含事务管理事务由 Application 层管理
*
* @author tuoheng
*/
public interface ApprovalDomainService {
/**
* 创建审批申请
* 业务规则
* 1. 生成唯一的审批编号
* 2. 初始化审批状态为草稿
* 3. 记录申请人和申请时间
* 4. 关联业务对象
*
* @param approval 审批领域模型
* @return 创建后的审批
*/
Approval createApproval(Approval approval);
/**
* 提交审批申请
* 业务规则
* 1. 验证审批状态为草稿
* 2. 验证申请数据完整性
* 3. 确定审批人根据审批类型和规则
* 4. 更新审批状态为待审批
* 5. 记录提交时间
* 6. 创建审批历史记录
*
* @param approvalId 审批ID
* @return 更新后的审批
*/
Approval submitApproval(Long approvalId);
/**
* 审批通过
* 业务规则
* 1. 验证审批状态为待审批
* 2. 验证审批人权限
* 3. 更新审批状态为已通过
* 4. 记录审批意见和审批时间
* 5. 创建审批历史记录
*
* @param approvalId 审批ID
* @param approverId 审批人ID
* @param comment 审批意见
* @return 更新后的审批
*/
Approval approveApproval(Long approvalId, Long approverId, String comment);
/**
* 审批拒绝
* 业务规则
* 1. 验证审批状态为待审批
* 2. 验证审批人权限
* 3. 更新审批状态为已拒绝
* 4. 记录拒绝原因和审批时间
* 5. 创建审批历史记录
*
* @param approvalId 审批ID
* @param approverId 审批人ID
* @param reason 拒绝原因
* @return 更新后的审批
*/
Approval rejectApproval(Long approvalId, Long approverId, String reason);
/**
* 撤回审批申请
* 业务规则
* 1. 验证审批状态为待审批
* 2. 验证申请人权限
* 3. 更新审批状态为已撤回
* 4. 记录撤回原因和时间
* 5. 创建审批历史记录
*
* @param approvalId 审批ID
* @param applicantId 申请人ID
* @param reason 撤回原因
* @return 更新后的审批
*/
Approval withdrawApproval(Long approvalId, Long applicantId, String reason);
/**
* 转审
* 业务规则
* 1. 验证审批状态为待审批
* 2. 验证当前审批人权限
* 3. 验证目标审批人有效
* 4. 更新审批人
* 5. 记录转审原因和时间
* 6. 创建审批历史记录
*
* @param approvalId 审批ID
* @param currentApproverId 当前审批人ID
* @param targetApproverId 目标审批人ID
* @param reason 转审原因
* @return 更新后的审批
*/
Approval transferApproval(Long approvalId, Long currentApproverId, Long targetApproverId, String reason);
/**
* 加签增加审批人
* 业务规则
* 1. 验证审批状态为待审批
* 2. 验证当前审批人权限
* 3. 添加新的审批人
* 4. 记录加签原因和时间
* 5. 创建审批历史记录
*
* @param approvalId 审批ID
* @param currentApproverId 当前审批人ID
* @param additionalApproverId 新增审批人ID
* @param reason 加签原因
* @return 更新后的审批
*/
Approval addApprover(Long approvalId, Long currentApproverId, Long additionalApproverId, String reason);
/**
* 删除审批申请
* 业务规则
* 1. 只能删除草稿状态的审批
* 2. 验证申请人权限
* 3. 逻辑删除审批记录
*
* @param approvalId 审批ID
* @param applicantId 申请人ID
*/
void deleteApproval(Long approvalId, Long applicantId);
/**
* 查询审批详情
*
* @param approvalId 审批ID
* @return 审批领域模型
*/
Approval getApprovalById(Long approvalId);
/**
* 查询审批历史
*
* @param approvalId 审批ID
* @return 审批历史列表
*/
List<ApprovalHistory> getApprovalHistory(Long approvalId);
/**
* 确定审批人
* 业务规则
* 1. 根据审批类型确定审批人
* 2. 航线审批航线管理员
* 3. 设备审批设备管理员
* 4. 任务审批任务管理员
* 5. 支持多级审批
*
* @param approvalType 审批类型
* @param bizId 业务ID
* @return 审批人ID列表
*/
List<Long> determineApprovers(String approvalType, Long bizId);
/**
* 验证审批人权限
* 业务规则
* 1. 检查用户是否是当前审批人
* 2. 检查用户是否有审批权限
*
* @param approvalId 审批ID
* @param userId 用户ID
* @return 是否有权限
*/
boolean hasApprovalPermission(Long approvalId, Long userId);
/**
* 验证申请人权限
* 业务规则
* 1. 检查用户是否是申请人
*
* @param approvalId 审批ID
* @param userId 用户ID
* @return 是否是申请人
*/
boolean isApplicant(Long approvalId, Long userId);
/**
* 检查审批是否超时
* 业务规则
* 1. 待审批状态超过3天视为超时
* 2. 发送超时提醒
*
* @param approvalId 审批ID
* @return 是否超时
*/
boolean isApprovalTimeout(Long approvalId);
/**
* 计算审批时长
* 从提交到审批完成的时长小时
*
* @param approvalId 审批ID
* @return 审批时长小时
*/
Long calculateApprovalDuration(Long approvalId);
/**
* 计算审批统计信息
* 业务规则
* 1. 统计审批总数
* 2. 按状态分组统计
* 3. 按审批类型分组统计
* 4. 计算平均审批时长
* 5. 按日期分组统计
*
* @param approvalType 审批类型可选
* @param startDate 开始日期可选
* @param endDate 结束日期可选
* @return 审批统计信息
*/
ApprovalStatistics calculateStatistics(String approvalType, String startDate, String endDate);
/**
* 创建审批历史记录
*
* @param approvalId 审批ID
* @param action 操作类型提交审批拒绝撤回转审
* @param operatorId 操作人ID
* @param comment 操作说明
*/
void createApprovalHistory(Long approvalId, String action, Long operatorId, String comment);
/**
* 检查是否可以催办
* 业务规则
* 1. 审批状态为待审批
* 2. 距离上次催办至少1小时
*
* @param approvalId 审批ID
* @return 是否可以催办
*/
boolean canUrge(Long approvalId);
/**
* 记录催办
*
* @param approvalId 审批ID
* @param applicantId 申请人ID
*/
void recordUrge(Long approvalId, Long applicantId);
/**
* 执行业务回调
* 审批通过或拒绝后执行相应的业务逻辑
* 例如航线审批通过后更新航线状态为已审核
*
* @param approvalId 审批ID
* @param approved 是否通过
*/
void executeBusinessCallback(Long approvalId, boolean approved);
/**
* 检查审批是否可以删除
* 业务规则
* 1. 只能删除草稿状态的审批
*
* @param approvalId 审批ID
* @return 是否可以删除
*/
boolean canDelete(Long approvalId);
/**
* 检查审批是否可以撤回
* 业务规则
* 1. 只能撤回待审批状态的审批
* 2. 只有申请人可以撤回
*
* @param approvalId 审批ID
* @param userId 用户ID
* @return 是否可以撤回
*/
boolean canWithdraw(Long approvalId, Long userId);
}

View File

@ -0,0 +1,244 @@
package com.tuoheng.airport.approval.presentation.controller;
import com.tuoheng.airport.approval.application.dto.*;
import com.tuoheng.airport.approval.application.service.ApprovalApplicationService;
import com.tuoheng.airport.approval.presentation.converter.ApprovalVoConverter;
import com.tuoheng.airport.approval.presentation.vo.*;
import com.tuoheng.common.response.Result;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.List;
/**
* 审批管理控制器Presentation层
* 提供审批流程管理的 REST API 接口
* 负责审批申请审核流程管理
*
* 职责
* 1. 接收前端的 VO 对象
* 2. VO 转换为 DTO 传递给 Application
* 3. Application 层返回的 DTO 转换为 VO 返回给前端
* 4. 不包含任何业务逻辑
*
* @author tuoheng
*/
@Slf4j
@RestController
@RequestMapping("/api/v1/approvals")
@RequiredArgsConstructor
@Validated
@Tag(name = "审批管理", description = "审批流程管理相关接口")
public class ApprovalController {
private final ApprovalApplicationService approvalApplicationService;
/**
* 创建审批申请
* POST /api/v1/approvals
*/
@PostMapping
@Operation(summary = "创建审批申请", description = "创建新的审批申请")
public Result<ApprovalVO> createApproval(@Valid @RequestBody ApprovalCreateVO vo) {
log.info("接收到创建审批申请请求: {}", vo);
ApprovalCreateRequest request = ApprovalVoConverter.toCreateRequest(vo);
ApprovalResponse response = approvalApplicationService.createApproval(request);
ApprovalVO result = ApprovalVoConverter.toVO(response);
return Result.success(result);
}
/**
* 提交审批申请
* POST /api/v1/approvals/{id}/submit
*/
@PostMapping("/{id}/submit")
@Operation(summary = "提交审批申请", description = "提交审批申请进入审批流程")
public Result<ApprovalVO> submitApproval(
@Parameter(description = "审批ID") @PathVariable Long id) {
log.info("接收到提交审批申请请求审批ID: {}", id);
ApprovalResponse response = approvalApplicationService.submitApproval(id);
ApprovalVO result = ApprovalVoConverter.toVO(response);
return Result.success(result);
}
/**
* 审批通过
* POST /api/v1/approvals/{id}/approve
*/
@PostMapping("/{id}/approve")
@Operation(summary = "审批通过", description = "审批通过指定的申请")
public Result<ApprovalVO> approveApproval(
@Parameter(description = "审批ID") @PathVariable Long id,
@Valid @RequestBody ApprovalDecisionVO vo) {
log.info("接收到审批通过请求审批ID: {}, 审批意见: {}", id, vo);
ApprovalDecisionRequest request = ApprovalVoConverter.toDecisionRequest(vo);
ApprovalResponse response = approvalApplicationService.approveApproval(id, request);
ApprovalVO result = ApprovalVoConverter.toVO(response);
return Result.success(result);
}
/**
* 审批拒绝
* POST /api/v1/approvals/{id}/reject
*/
@PostMapping("/{id}/reject")
@Operation(summary = "审批拒绝", description = "审批拒绝指定的申请")
public Result<ApprovalVO> rejectApproval(
@Parameter(description = "审批ID") @PathVariable Long id,
@Valid @RequestBody ApprovalDecisionVO vo) {
log.info("接收到审批拒绝请求审批ID: {}, 拒绝原因: {}", id, vo);
ApprovalDecisionRequest request = ApprovalVoConverter.toDecisionRequest(vo);
ApprovalResponse response = approvalApplicationService.rejectApproval(id, request);
ApprovalVO result = ApprovalVoConverter.toVO(response);
return Result.success(result);
}
/**
* 撤回审批申请
* POST /api/v1/approvals/{id}/withdraw
*/
@PostMapping("/{id}/withdraw")
@Operation(summary = "撤回审批申请", description = "撤回已提交的审批申请")
public Result<ApprovalVO> withdrawApproval(
@Parameter(description = "审批ID") @PathVariable Long id,
@RequestParam(required = false) String reason) {
log.info("接收到撤回审批申请请求审批ID: {}, 撤回原因: {}", id, reason);
ApprovalResponse response = approvalApplicationService.withdrawApproval(id, reason);
ApprovalVO result = ApprovalVoConverter.toVO(response);
return Result.success(result);
}
/**
* 转审
* POST /api/v1/approvals/{id}/transfer
*/
@PostMapping("/{id}/transfer")
@Operation(summary = "转审", description = "将审批转交给其他审批人")
public Result<ApprovalVO> transferApproval(
@Parameter(description = "审批ID") @PathVariable Long id,
@Parameter(description = "目标审批人ID") @RequestParam Long targetApproverId,
@RequestParam(required = false) String reason) {
log.info("接收到转审请求审批ID: {}, 目标审批人ID: {}, 转审原因: {}", id, targetApproverId, reason);
ApprovalResponse response = approvalApplicationService.transferApproval(id, targetApproverId, reason);
ApprovalVO result = ApprovalVoConverter.toVO(response);
return Result.success(result);
}
/**
* 查询审批详情
* GET /api/v1/approvals/{id}
*/
@GetMapping("/{id}")
@Operation(summary = "查询审批详情", description = "根据审批ID查询审批详细信息")
public Result<ApprovalVO> getApprovalById(
@Parameter(description = "审批ID") @PathVariable Long id) {
log.info("接收到查询审批详情请求审批ID: {}", id);
ApprovalResponse response = approvalApplicationService.getApprovalById(id);
ApprovalVO result = ApprovalVoConverter.toVO(response);
return Result.success(result);
}
/**
* 查询审批列表
* GET /api/v1/approvals
*/
@GetMapping
@Operation(summary = "查询审批列表", description = "根据条件查询审批列表")
public Result<List<ApprovalVO>> queryApprovals(ApprovalQueryVO vo) {
log.info("接收到查询审批列表请求,查询条件: {}", vo);
ApprovalQueryRequest request = ApprovalVoConverter.toQueryRequest(vo);
List<ApprovalResponse> responses = approvalApplicationService.queryApprovals(request);
List<ApprovalVO> results = ApprovalVoConverter.toVOList(responses);
return Result.success(results);
}
/**
* 查询我的待审批列表
* GET /api/v1/approvals/pending
*/
@GetMapping("/pending")
@Operation(summary = "查询待审批列表", description = "查询当前用户的待审批列表")
public Result<List<ApprovalVO>> getMyPendingApprovals() {
log.info("接收到查询待审批列表请求");
List<ApprovalResponse> responses = approvalApplicationService.getMyPendingApprovals();
List<ApprovalVO> results = ApprovalVoConverter.toVOList(responses);
return Result.success(results);
}
/**
* 查询我的申请列表
* GET /api/v1/approvals/my-applications
*/
@GetMapping("/my-applications")
@Operation(summary = "查询我的申请列表", description = "查询当前用户提交的所有审批申请")
public Result<List<ApprovalVO>> getMyApplications(
@Parameter(description = "审批状态") @RequestParam(required = false) String status) {
log.info("接收到查询我的申请列表请求,状态: {}", status);
List<ApprovalResponse> responses = approvalApplicationService.getMyApplications(status);
List<ApprovalVO> results = ApprovalVoConverter.toVOList(responses);
return Result.success(results);
}
/**
* 查询审批历史
* GET /api/v1/approvals/{id}/history
*/
@GetMapping("/{id}/history")
@Operation(summary = "查询审批历史", description = "查询审批的流转历史记录")
public Result<List<ApprovalHistoryVO>> getApprovalHistory(
@Parameter(description = "审批ID") @PathVariable Long id) {
log.info("接收到查询审批历史请求审批ID: {}", id);
List<ApprovalHistoryResponse> responses = approvalApplicationService.getApprovalHistory(id);
List<ApprovalHistoryVO> results = ApprovalVoConverter.toHistoryVOList(responses);
return Result.success(results);
}
/**
* 查询审批统计信息
* GET /api/v1/approvals/statistics
*/
@GetMapping("/statistics")
@Operation(summary = "查询审批统计", description = "查询审批统计信息")
public Result<ApprovalStatisticsVO> getApprovalStatistics(
@Parameter(description = "审批类型") @RequestParam(required = false) String approvalType,
@Parameter(description = "开始日期") @RequestParam(required = false) String startDate,
@Parameter(description = "结束日期") @RequestParam(required = false) String endDate) {
log.info("接收到查询审批统计请求,审批类型: {}, 开始日期: {}, 结束日期: {}", approvalType, startDate, endDate);
ApprovalStatisticsResponse response = approvalApplicationService.getApprovalStatistics(approvalType, startDate, endDate);
ApprovalStatisticsVO result = ApprovalVoConverter.toStatisticsVO(response);
return Result.success(result);
}
/**
* 删除审批申请
* DELETE /api/v1/approvals/{id}
*/
@DeleteMapping("/{id}")
@Operation(summary = "删除审批申请", description = "删除指定的审批申请(仅草稿状态可删除)")
public Result<Void> deleteApproval(
@Parameter(description = "审批ID") @PathVariable Long id) {
log.info("接收到删除审批申请请求审批ID: {}", id);
approvalApplicationService.deleteApproval(id);
return Result.success();
}
/**
* 批量审批
* POST /api/v1/approvals/batch-approve
*/
@PostMapping("/batch-approve")
@Operation(summary = "批量审批", description = "批量审批通过多个申请")
public Result<BatchApprovalResultVO> batchApprove(@Valid @RequestBody BatchApprovalVO vo) {
log.info("接收到批量审批请求,审批数量: {}", vo.getApprovalIds().size());
BatchApprovalRequest request = ApprovalVoConverter.toBatchRequest(vo);
BatchApprovalResultResponse response = approvalApplicationService.batchApprove(request);
BatchApprovalResultVO result = ApprovalVoConverter.toBatchResultVO(response);
return Result.success(result);
}
}

View File

@ -0,0 +1,57 @@
package com.tuoheng.airport.approval.presentation.converter;
import com.tuoheng.airport.approval.application.dto.*;
import com.tuoheng.airport.approval.presentation.vo.*;
import java.util.List;
import java.util.stream.Collectors;
public class ApprovalVoConverter {
public static ApprovalCreateRequest toCreateRequest(ApprovalCreateVO vo) {
// TODO: 实现转换逻辑
return new ApprovalCreateRequest();
}
public static ApprovalDecisionRequest toDecisionRequest(ApprovalDecisionVO vo) {
// TODO: 实现转换逻辑
return new ApprovalDecisionRequest();
}
public static ApprovalQueryRequest toQueryRequest(ApprovalQueryVO vo) {
// TODO: 实现转换逻辑
return new ApprovalQueryRequest();
}
public static ApprovalVO toVO(ApprovalResponse response) {
// TODO: 实现转换逻辑
return new ApprovalVO();
}
public static List<ApprovalVO> toVOList(List<ApprovalResponse> responses) {
return responses.stream().map(ApprovalVoConverter::toVO).collect(Collectors.toList());
}
public static ApprovalHistoryVO toHistoryVO(ApprovalHistoryResponse response) {
// TODO: 实现转换逻辑
return new ApprovalHistoryVO();
}
public static List<ApprovalHistoryVO> toHistoryVOList(List<ApprovalHistoryResponse> responses) {
return responses.stream().map(ApprovalVoConverter::toHistoryVO).collect(Collectors.toList());
}
public static ApprovalStatisticsVO toStatisticsVO(ApprovalStatisticsResponse response) {
// TODO: 实现转换逻辑
return new ApprovalStatisticsVO();
}
public static BatchApprovalRequest toBatchRequest(BatchApprovalVO vo) {
// TODO: 实现转换逻辑
return new BatchApprovalRequest();
}
public static BatchApprovalResultVO toBatchResultVO(BatchApprovalResultResponse response) {
// TODO: 实现转换逻辑
return new BatchApprovalResultVO();
}
}

View File

@ -0,0 +1,8 @@
package com.tuoheng.airport.approval.presentation.vo;
import lombok.Data;
@Data
public class ApprovalCreateVO {
// TODO: 添加字段
}

View File

@ -0,0 +1,8 @@
package com.tuoheng.airport.approval.presentation.vo;
import lombok.Data;
@Data
public class ApprovalDecisionVO {
private String comment;
}

View File

@ -0,0 +1,8 @@
package com.tuoheng.airport.approval.presentation.vo;
import lombok.Data;
@Data
public class ApprovalHistoryVO {
// TODO: 添加字段
}

View File

@ -0,0 +1,8 @@
package com.tuoheng.airport.approval.presentation.vo;
import lombok.Data;
@Data
public class ApprovalQueryVO {
// TODO: 添加字段
}

View File

@ -0,0 +1,8 @@
package com.tuoheng.airport.approval.presentation.vo;
import lombok.Data;
@Data
public class ApprovalStatisticsVO {
// TODO: 添加字段
}

View File

@ -0,0 +1,8 @@
package com.tuoheng.airport.approval.presentation.vo;
import lombok.Data;
@Data
public class ApprovalVO {
// TODO: 添加字段
}

View File

@ -0,0 +1,9 @@
package com.tuoheng.airport.approval.presentation.vo;
import lombok.Data;
@Data
public class BatchApprovalResultVO {
private Integer successCount;
private Integer failureCount;
}

View File

@ -0,0 +1,10 @@
package com.tuoheng.airport.approval.presentation.vo;
import lombok.Data;
import java.util.List;
@Data
public class BatchApprovalVO {
private List<Long> approvalIds;
private String comment;
}

View File

@ -0,0 +1,48 @@
package com.tuoheng.airport.fms.application.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 紧急飞行请求 DTO
*
* @author tuoheng
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class EmergencyFlightRequest {
/**
* 起点纬度
*/
private Double startLatitude;
/**
* 起点经度
*/
private Double startLongitude;
/**
* 终点纬度
*/
private Double endLatitude;
/**
* 终点经度
*/
private Double endLongitude;
/**
* 飞行高度
*/
private Double altitude;
/**
* 设备ID可选不指定则自动选择最近的可用设备
*/
private Long deviceId;
}

View File

@ -0,0 +1,38 @@
package com.tuoheng.airport.fms.application.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 飞行执行请求 DTO
*
* @author tuoheng
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class FlightExecuteRequest {
/**
* 任务ID可选
*/
private Long taskId;
/**
* 航线ID
*/
private Long airlineId;
/**
* 设备ID
*/
private Long deviceId;
/**
* 计划开始时间
*/
private String planStartTime;
}

View File

@ -0,0 +1,48 @@
package com.tuoheng.airport.fms.application.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 飞行记录查询请求 DTO
*
* @author tuoheng
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class FlightRecordQueryRequest {
/**
* 飞行编号
*/
private String flightCode;
/**
* 任务ID
*/
private Long taskId;
/**
* 设备ID
*/
private Long deviceId;
/**
* 飞行状态
*/
private String status;
/**
* 开始日期
*/
private String startDate;
/**
* 结束日期
*/
private String endDate;
}

View File

@ -0,0 +1,8 @@
package com.tuoheng.airport.fms.application.dto;
import lombok.Data;
@Data
public class FlightRecordResponse {
// TODO: 添加字段
}

View File

@ -0,0 +1,58 @@
package com.tuoheng.airport.fms.application.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 飞行统计响应 DTO
*
* @author tuoheng
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class FlightStatisticsResponse {
/**
* 飞行总次数
*/
private Long totalFlights;
/**
* 成功次数
*/
private Long successCount;
/**
* 失败次数
*/
private Long failureCount;
/**
* 取消次数
*/
private Long cancelledCount;
/**
* 总飞行时长
*/
private Long totalDuration;
/**
* 总飞行距离
*/
private Double totalDistance;
/**
* 平均飞行时长
*/
private Long averageDuration;
/**
* 成功率%
*/
private Double successRate;
}

View File

@ -0,0 +1,75 @@
package com.tuoheng.airport.fms.application.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
/**
* 飞行状态响应 DTO
*
* @author tuoheng
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class FlightStatusResponse {
/**
* 飞行记录ID
*/
private Long flightRecordId;
/**
* 飞行状态
*/
private String status;
/**
* 当前纬度
*/
private Double latitude;
/**
* 当前经度
*/
private Double longitude;
/**
* 当前高度
*/
private Double altitude;
/**
* 当前速度m/s
*/
private Double speed;
/**
* 电池电量%
*/
private Integer batteryLevel;
/**
* 当前航点索引
*/
private Integer currentWaypointIndex;
/**
* 已飞行距离
*/
private Double flownDistance;
/**
* 已飞行时长
*/
private Long flownDuration;
/**
* 更新时间
*/
private LocalDateTime updateTime;
}

View File

@ -0,0 +1,53 @@
package com.tuoheng.airport.fms.application.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 飞行状态更新请求 DTO由设备上报触发
*
* @author tuoheng
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class FlightStatusUpdateRequest {
/**
* 飞行记录ID
*/
private Long flightRecordId;
/**
* 当前纬度
*/
private Double latitude;
/**
* 当前经度
*/
private Double longitude;
/**
* 当前高度
*/
private Double altitude;
/**
* 当前速度m/s
*/
private Double speed;
/**
* 电池电量%
*/
private Integer batteryLevel;
/**
* 当前航点索引
*/
private Integer currentWaypointIndex;
}

View File

@ -0,0 +1,29 @@
package com.tuoheng.airport.fms.application.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
/**
* 飞行轨迹点响应 DTO
*
* @author tuoheng
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class TrajectoryPointResponse {
private Long id;
private Long flightRecordId;
private Double latitude;
private Double longitude;
private Double altitude;
private Double speed;
private Integer batteryLevel;
private LocalDateTime recordTime;
}

View File

@ -0,0 +1,263 @@
package com.tuoheng.airport.fms.application.service;
import com.tuoheng.airport.fms.application.dto.*;
import java.util.List;
/**
* 飞行应用服务接口Application层
* 定义单次飞行相关的用例Use Cases
* 协调领域模型完成飞行执行控制监控操作
*
* @author tuoheng
*/
public interface FlightApplicationService {
/**
* 执行单次飞行
* 业务逻辑
* 1. 验证航线有效性已审核
* 2. 检查设备可用性在线激活
* 3. 检查空域冲突
* 4. 分配设备资源锁定设备
* 5. 下发飞行指令到设备
* 6. 创建飞行记录
* 7. 启动实时监控
*
* @param request 飞行执行请求
* @return 飞行记录响应
*/
FlightRecordResponse executeFlight(FlightExecuteRequest request);
/**
* 紧急飞行
* 业务逻辑
* 1. 动态生成紧急航线最短路径
* 2. 选择最近的可用设备
* 3. 优先级最高可中断其他飞行
* 4. 立即执行飞行
*
* @param request 紧急飞行请求
* @return 飞行记录响应
*/
FlightRecordResponse emergencyFlight(EmergencyFlightRequest request);
/**
* 取消飞行
* 业务逻辑
* 1. 验证飞行状态准备中或执行中
* 2. 发送取消指令到设备
* 3. 释放设备资源
* 4. 更新飞行记录状态为已取消
* 5. 记录取消原因
*
* @param recordId 飞行记录ID
* @param reason 取消原因
*/
void cancelFlight(Long recordId, String reason);
/**
* 暂停飞行
* 业务逻辑
* 1. 验证飞行状态为执行中
* 2. 发送暂停指令到设备
* 3. 更新飞行状态为已暂停
*
* @param recordId 飞行记录ID
*/
void pauseFlight(Long recordId);
/**
* 恢复飞行
* 业务逻辑
* 1. 验证飞行状态为已暂停
* 2. 发送恢复指令到设备
* 3. 更新飞行状态为执行中
*
* @param recordId 飞行记录ID
*/
void resumeFlight(Long recordId);
/**
* 返航
* 业务逻辑
* 1. 验证飞行状态为执行中
* 2. 发送返航指令到设备
* 3. 记录返航时间
*
* @param recordId 飞行记录ID
*/
void returnHome(Long recordId);
/**
* 无人机起飞
* 业务逻辑
* 1. 验证飞行状态为准备中
* 2. 发送起飞指令到设备
* 3. 更新飞行状态为执行中
* 4. 记录起飞时间
*
* @param recordId 飞行记录ID
*/
void takeoff(Long recordId);
/**
* 无人机降落
* 业务逻辑
* 1. 验证飞行状态为执行中
* 2. 发送降落指令到设备
* 3. 记录降落时间
*
* @param recordId 飞行记录ID
*/
void land(Long recordId);
/**
* 调整飞行高度
* 业务逻辑
* 1. 验证飞行状态为执行中
* 2. 验证目标高度合法性0-500米
* 3. 发送调整高度指令到设备
*
* @param recordId 飞行记录ID
* @param altitude 目标高度
*/
void adjustAltitude(Long recordId, Integer altitude);
/**
* 调整飞行速度
* 业务逻辑
* 1. 验证飞行状态为执行中
* 2. 验证目标速度合法性1-20m/s
* 3. 发送调整速度指令到设备
*
* @param recordId 飞行记录ID
* @param speed 目标速度m/s
*/
void adjustSpeed(Long recordId, Double speed);
/**
* 跳转到指定航点
* 业务逻辑
* 1. 验证飞行状态为执行中
* 2. 验证航点索引合法性
* 3. 发送跳转航点指令到设备
*
* @param recordId 飞行记录ID
* @param waypointIndex 航点索引
*/
void gotoWaypoint(Long recordId, Integer waypointIndex);
/**
* 查询飞行记录详情
*
* @param recordId 飞行记录ID
* @return 飞行记录响应
*/
FlightRecordResponse getFlightRecord(Long recordId);
/**
* 查询飞行实时状态
* 业务逻辑
* 1. 查询飞行记录
* 2. 从缓存或设备获取实时状态
* 3. 返回位置高度速度电量等信息
*
* @param recordId 飞行记录ID
* @return 飞行状态响应
*/
FlightStatusResponse getFlightStatus(Long recordId);
/**
* 查询飞行轨迹
* 业务逻辑
* 1. 查询飞行记录
* 2. 查询所有轨迹点
* 3. 按时间排序返回
*
* @param recordId 飞行记录ID
* @return 轨迹点列表
*/
List<TrajectoryPointResponse> getFlightTrajectory(Long recordId);
/**
* 根据条件查询飞行记录列表
*
* @param request 查询请求
* @return 飞行记录列表
*/
List<FlightRecordResponse> queryFlightRecords(FlightRecordQueryRequest request);
/**
* 根据设备ID查询飞行记录列表
*
* @param deviceId 设备ID
* @return 飞行记录列表
*/
List<FlightRecordResponse> getFlightRecordsByDevice(Long deviceId);
/**
* 查询正在执行的飞行列表
* 业务逻辑
* 1. 查询状态为执行中的飞行记录
* 2. 返回实时状态信息
*
* @return 飞行记录列表
*/
List<FlightRecordResponse> getActiveFlights();
/**
* 查询飞行统计信息
* 业务逻辑
* 1. 统计飞行总次数
* 2. 统计成功/失败次数
* 3. 统计总飞行时长
* 4. 统计总飞行距离
* 5. 按日期分组统计
*
* @param deviceId 设备ID可选
* @param startDate 开始日期可选
* @param endDate 结束日期可选
* @return 飞行统计信息
*/
FlightStatisticsResponse getFlightStatistics(Long deviceId, String startDate, String endDate);
/**
* 更新飞行实时状态由设备上报触发
* 业务逻辑
* 1. 接收设备上报的实时数据
* 2. 更新飞行状态
* 3. 记录轨迹点
* 4. 检查异常情况低电量失联等
*
* @param request 状态更新请求
*/
void updateFlightStatus(FlightStatusUpdateRequest request);
/**
* 完成飞行由设备上报触发
* 业务逻辑
* 1. 验证飞行状态
* 2. 记录降落时间
* 3. 计算飞行时长和距离
* 4. 释放设备资源
* 5. 更新飞行状态为已完成
* 6. 触发媒体文件上传
*
* @param recordId 飞行记录ID
*/
void completeFlight(Long recordId);
/**
* 飞行失败由设备上报或系统检测触发
* 业务逻辑
* 1. 记录失败原因
* 2. 释放设备资源
* 3. 更新飞行状态为失败
* 4. 发送告警通知
*
* @param recordId 飞行记录ID
* @param reason 失败原因
*/
void failFlight(Long recordId, String reason);
}

View File

@ -0,0 +1,110 @@
package com.tuoheng.airport.fms.domain.model;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
/**
* 飞行记录领域模型
*
* @author tuoheng
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class FlightRecord {
private Long id;
private String flightCode;
private Long taskId;
private Long airlineId;
private Long deviceId;
private FlightStatus status;
private LocalDateTime planStartTime;
private LocalDateTime actualStartTime;
private LocalDateTime actualEndTime;
private Long actualDuration;
private Double actualDistance;
private String failureReason;
private Long tenantId;
private LocalDateTime createTime;
private LocalDateTime updateTime;
// ==================== 业务方法 ====================
public void start() {
if (this.status != FlightStatus.PREPARING) {
throw new IllegalStateException("只有准备中的飞行可以开始");
}
this.status = FlightStatus.EXECUTING;
this.actualStartTime = LocalDateTime.now();
this.updateTime = LocalDateTime.now();
}
public void complete() {
if (this.status != FlightStatus.EXECUTING) {
throw new IllegalStateException("只有执行中的飞行可以完成");
}
this.status = FlightStatus.COMPLETED;
this.actualEndTime = LocalDateTime.now();
if (this.actualStartTime != null) {
this.actualDuration = java.time.Duration.between(this.actualStartTime, this.actualEndTime).getSeconds();
}
this.updateTime = LocalDateTime.now();
}
public void cancel(String reason) {
if (this.status == FlightStatus.COMPLETED || this.status == FlightStatus.FAILED) {
throw new IllegalStateException("已完成或已失败的飞行不能取消");
}
this.status = FlightStatus.CANCELLED;
this.failureReason = reason;
this.updateTime = LocalDateTime.now();
}
public void fail(String reason) {
this.status = FlightStatus.FAILED;
this.failureReason = reason;
this.actualEndTime = LocalDateTime.now();
this.updateTime = LocalDateTime.now();
}
public void pause() {
if (this.status != FlightStatus.EXECUTING) {
throw new IllegalStateException("只有执行中的飞行可以暂停");
}
this.status = FlightStatus.PAUSED;
this.updateTime = LocalDateTime.now();
}
public void resume() {
if (this.status != FlightStatus.PAUSED) {
throw new IllegalStateException("只有已暂停的飞行可以恢复");
}
this.status = FlightStatus.EXECUTING;
this.updateTime = LocalDateTime.now();
}
public boolean isActive() {
return this.status == FlightStatus.EXECUTING || this.status == FlightStatus.PAUSED;
}
public static FlightRecord create(String flightCode, Long taskId, Long airlineId, Long deviceId, Long tenantId) {
LocalDateTime now = LocalDateTime.now();
return FlightRecord.builder()
.flightCode(flightCode)
.taskId(taskId)
.airlineId(airlineId)
.deviceId(deviceId)
.status(FlightStatus.PREPARING)
.planStartTime(now)
.tenantId(tenantId)
.createTime(now)
.updateTime(now)
.build();
}
}

View File

@ -0,0 +1,68 @@
package com.tuoheng.airport.fms.domain.model;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 飞行统计领域模型
*
* @author tuoheng
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class FlightStatistics {
/**
* 飞行总次数
*/
private Long totalFlights;
/**
* 成功次数
*/
private Long successCount;
/**
* 失败次数
*/
private Long failureCount;
/**
* 取消次数
*/
private Long cancelledCount;
/**
* 总飞行时长
*/
private Long totalDuration;
/**
* 总飞行距离
*/
private Double totalDistance;
/**
* 平均飞行时长
*/
public Long getAverageDuration() {
if (successCount == null || successCount == 0) {
return 0L;
}
return totalDuration / successCount;
}
/**
* 成功率
*/
public double getSuccessRate() {
if (totalFlights == null || totalFlights == 0) {
return 0.0;
}
return (double) successCount / totalFlights * 100;
}
}

View File

@ -0,0 +1,26 @@
package com.tuoheng.airport.fms.domain.model;
/**
* 飞行状态枚举
*
* @author tuoheng
*/
public enum FlightStatus {
PREPARING("准备中"),
EXECUTING("执行中"),
PAUSED("已暂停"),
COMPLETED("已完成"),
CANCELLED("已取消"),
FAILED("失败");
private final String description;
FlightStatus(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
}

View File

@ -0,0 +1,42 @@
package com.tuoheng.airport.fms.domain.model;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
/**
* 飞行轨迹点领域模型
*
* @author tuoheng
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class TrajectoryPoint {
private Long id;
private Long flightRecordId;
private Double latitude;
private Double longitude;
private Double altitude;
private Double speed;
private Integer batteryLevel;
private LocalDateTime recordTime;
public static TrajectoryPoint create(Long flightRecordId, Double latitude, Double longitude,
Double altitude, Double speed, Integer batteryLevel) {
return TrajectoryPoint.builder()
.flightRecordId(flightRecordId)
.latitude(latitude)
.longitude(longitude)
.altitude(altitude)
.speed(speed)
.batteryLevel(batteryLevel)
.recordTime(LocalDateTime.now())
.build();
}
}

View File

@ -0,0 +1,36 @@
package com.tuoheng.airport.fms.domain.repository;
import com.tuoheng.airport.fms.domain.model.FlightRecord;
import java.util.List;
import java.util.Optional;
/**
* 飞行记录仓储接口
*
* @author tuoheng
*/
public interface FlightRecordRepository {
FlightRecord save(FlightRecord flightRecord);
Optional<FlightRecord> findById(Long id);
Optional<FlightRecord> findByFlightCode(String flightCode);
List<FlightRecord> findByTaskId(Long taskId);
List<FlightRecord> findByDeviceId(Long deviceId);
List<FlightRecord> findByStatus(String status);
List<FlightRecord> findActiveFlights();
void delete(Long id);
long countByTaskId(Long taskId);
long countByDeviceId(Long deviceId);
long countByStatus(String status);
}

View File

@ -0,0 +1,21 @@
package com.tuoheng.airport.fms.domain.repository;
import com.tuoheng.airport.fms.domain.model.TrajectoryPoint;
import java.util.List;
/**
* 飞行轨迹点仓储接口
*
* @author tuoheng
*/
public interface TrajectoryPointRepository {
TrajectoryPoint save(TrajectoryPoint trajectoryPoint);
List<TrajectoryPoint> findByFlightRecordId(Long flightRecordId);
void deleteByFlightRecordId(Long flightRecordId);
long countByFlightRecordId(Long flightRecordId);
}

View File

@ -0,0 +1,359 @@
package com.tuoheng.airport.fms.domain.service;
import com.tuoheng.airport.fms.domain.model.FlightRecord;
import com.tuoheng.airport.fms.domain.model.FlightStatus;
import com.tuoheng.airport.fms.domain.model.TrajectoryPoint;
import com.tuoheng.airport.fms.domain.model.FlightStatistics;
import java.util.List;
/**
* 飞行领域服务接口Domain层
* 封装飞行执行相关的业务逻辑和业务规则
*
* Domain Service 的职责
* 1. 封装飞行执行的核心业务规则
* 2. 协调飞行记录设备航线的关系
* 3. 保证飞行数据的一致性
* 4. 不包含事务管理事务由 Application 层管理
*
* @author tuoheng
*/
public interface FlightDomainService {
/**
* 验证飞行可以执行
* 业务规则
* 1. 航线必须已审核通过
* 2. 设备必须在线且可用激活状态
* 3. 设备必须已分配到机场
* 4. 天气条件满足飞行要求风速降雨等
* 5. 空域没有冲突
* 6. 设备电量充足>20%
*
* @param airlineId 航线ID
* @param deviceId 设备ID
* @return 是否可以执行
*/
boolean canExecuteFlight(Long airlineId, Long deviceId);
/**
* 创建飞行记录
* 业务规则
* 1. 生成唯一的飞行编号
* 2. 记录飞行计划信息航线设备预计时长
* 3. 初始化飞行状态为准备中
* 4. 锁定设备资源
*
* @param flightRecord 飞行记录领域模型
* @return 创建后的飞行记录
*/
FlightRecord createFlightRecord(FlightRecord flightRecord);
/**
* 开始飞行
* 业务规则
* 1. 验证飞行状态为准备中
* 2. 下发飞行指令到设备
* 3. 更新飞行状态为执行中
* 4. 记录起飞时间
* 5. 启动实时监控
*
* @param recordId 飞行记录ID
* @return 更新后的飞行记录
*/
FlightRecord startFlight(Long recordId);
/**
* 取消飞行
* 业务规则
* 1. 只有准备中或执行中的飞行可以取消
* 2. 发送取消指令到设备
* 3. 释放设备资源
* 4. 更新飞行状态为已取消
* 5. 记录取消原因和时间
*
* @param recordId 飞行记录ID
* @param reason 取消原因
* @return 更新后的飞行记录
*/
FlightRecord cancelFlight(Long recordId, String reason);
/**
* 暂停飞行
* 业务规则
* 1. 只有执行中的飞行可以暂停
* 2. 发送暂停指令到设备悬停
* 3. 更新飞行状态为已暂停
* 4. 记录暂停时间
*
* @param recordId 飞行记录ID
* @return 更新后的飞行记录
*/
FlightRecord pauseFlight(Long recordId);
/**
* 恢复飞行
* 业务规则
* 1. 只有已暂停的飞行可以恢复
* 2. 发送恢复指令到设备
* 3. 更新飞行状态为执行中
* 4. 记录恢复时间
*
* @param recordId 飞行记录ID
* @return 更新后的飞行记录
*/
FlightRecord resumeFlight(Long recordId);
/**
* 完成飞行
* 业务规则
* 1. 记录降落时间
* 2. 计算实际飞行时长
* 3. 计算实际飞行距离
* 4. 释放设备资源
* 5. 更新飞行状态为已完成
* 6. 触发媒体文件上传任务
*
* @param recordId 飞行记录ID
* @return 更新后的飞行记录
*/
FlightRecord completeFlight(Long recordId);
/**
* 飞行失败
* 业务规则
* 1. 记录失败原因和时间
* 2. 释放设备资源
* 3. 更新飞行状态为失败
* 4. 发送告警通知
* 5. 记录异常日志
*
* @param recordId 飞行记录ID
* @param reason 失败原因
* @return 更新后的飞行记录
*/
FlightRecord failFlight(Long recordId, String reason);
/**
* 更新飞行实时状态
* 接收设备上报的实时数据位置高度速度电量等
* 业务规则
* 1. 验证飞行状态为执行中
* 2. 更新实时状态信息
* 3. 记录轨迹点
* 4. 检查异常情况低电量失联偏航等
*
* @param recordId 飞行记录ID
* @param status 飞行状态
* @return 更新后的飞行记录
*/
FlightRecord updateFlightStatus(Long recordId, FlightStatus status);
/**
* 记录飞行轨迹点
* 业务规则
* 1. 验证飞行状态为执行中
* 2. 记录轨迹点位置高度速度时间
* 3. 轨迹点按时间排序
*
* @param recordId 飞行记录ID
* @param point 轨迹点
*/
void recordTrajectoryPoint(Long recordId, TrajectoryPoint point);
/**
* 查询飞行记录详情
*
* @param recordId 飞行记录ID
* @return 飞行记录领域模型
*/
FlightRecord getFlightRecordById(Long recordId);
/**
* 查询飞行实时状态
*
* @param recordId 飞行记录ID
* @return 飞行状态
*/
FlightStatus getFlightStatus(Long recordId);
/**
* 查询飞行轨迹
*
* @param recordId 飞行记录ID
* @return 轨迹点列表
*/
List<TrajectoryPoint> getFlightTrajectory(Long recordId);
/**
* 查询正在执行的飞行列表
*
* @return 飞行记录列表
*/
List<FlightRecord> getActiveFlights();
/**
* 检查飞行是否超时
* 业务规则
* 1. 飞行时长超过预计时长的 150%
* 2. 自动标记为异常
* 3. 发送告警通知
*
* @param recordId 飞行记录ID
* @return 是否超时
*/
boolean isFlightTimeout(Long recordId);
/**
* 检查设备是否失联
* 业务规则
* 1. 超过30秒未收到设备心跳
* 2. 标记为失联状态
* 3. 发送告警通知
*
* @param recordId 飞行记录ID
* @return 是否失联
*/
boolean isDeviceDisconnected(Long recordId);
/**
* 检查设备电量是否充足
* 业务规则
* 1. 电量低于20%时发送低电量告警
* 2. 电量低于10%时强制返航
*
* @param recordId 飞行记录ID
* @return 电量是否充足
*/
boolean isBatteryLevelSufficient(Long recordId);
/**
* 检查飞行是否偏航
* 业务规则
* 1. 实际位置与计划航线偏离超过50米
* 2. 标记为偏航状态
* 3. 发送告警通知
*
* @param recordId 飞行记录ID
* @return 是否偏航
*/
boolean isFlightOffCourse(Long recordId);
/**
* 检查空域冲突
* 业务规则
* 1. 检查同一时间段内是否有其他飞行
* 2. 检查航线是否有重叠
* 3. 检查飞行高度是否有冲突
*
* @param airlineId 航线ID
* @param startTime 开始时间
* @return 是否有冲突
*/
boolean hasAirspaceConflict(Long airlineId, String startTime);
/**
* 计算飞行统计信息
* 业务规则
* 1. 统计飞行总次数
* 2. 统计成功/失败次数
* 3. 统计总飞行时长
* 4. 统计总飞行距离
* 5. 计算平均飞行时长
* 6. 按日期分组统计
*
* @param deviceId 设备ID可选
* @param startDate 开始日期可选
* @param endDate 结束日期可选
* @return 飞行统计信息
*/
FlightStatistics calculateStatistics(Long deviceId, String startDate, String endDate);
/**
* 发送飞行控制指令
* 业务规则
* 1. 验证飞行状态
* 2. 构建控制指令
* 3. 通过MQTT发送到设备
* 4. 记录指令日志
*
* @param recordId 飞行记录ID
* @param command 控制指令
* @param params 指令参数
*/
void sendFlightCommand(Long recordId, String command, Object params);
/**
* 返航
* 业务规则
* 1. 验证飞行状态为执行中
* 2. 发送返航指令到设备
* 3. 记录返航时间
*
* @param recordId 飞行记录ID
* @return 更新后的飞行记录
*/
FlightRecord returnHome(Long recordId);
/**
* 起飞
* 业务规则
* 1. 验证飞行状态为准备中
* 2. 发送起飞指令到设备
* 3. 更新飞行状态为执行中
* 4. 记录起飞时间
*
* @param recordId 飞行记录ID
* @return 更新后的飞行记录
*/
FlightRecord takeoff(Long recordId);
/**
* 降落
* 业务规则
* 1. 验证飞行状态为执行中
* 2. 发送降落指令到设备
* 3. 记录降落时间
*
* @param recordId 飞行记录ID
* @return 更新后的飞行记录
*/
FlightRecord land(Long recordId);
/**
* 调整飞行高度
* 业务规则
* 1. 验证飞行状态为执行中
* 2. 验证目标高度合法性0-500米
* 3. 发送调整高度指令到设备
*
* @param recordId 飞行记录ID
* @param altitude 目标高度
*/
void adjustAltitude(Long recordId, Integer altitude);
/**
* 调整飞行速度
* 业务规则
* 1. 验证飞行状态为执行中
* 2. 验证目标速度合法性1-20m/s
* 3. 发送调整速度指令到设备
*
* @param recordId 飞行记录ID
* @param speed 目标速度m/s
*/
void adjustSpeed(Long recordId, Double speed);
/**
* 跳转到指定航点
* 业务规则
* 1. 验证飞行状态为执行中
* 2. 验证航点索引合法性
* 3. 发送跳转航点指令到设备
*
* @param recordId 飞行记录ID
* @param waypointIndex 航点索引
*/
void gotoWaypoint(Long recordId, Integer waypointIndex);
}

View File

@ -0,0 +1,289 @@
package com.tuoheng.airport.fms.presentation.controller;
import com.tuoheng.airport.fms.application.dto.*;
import com.tuoheng.airport.fms.application.service.FlightApplicationService;
import com.tuoheng.airport.fms.presentation.converter.FlightVoConverter;
import com.tuoheng.airport.fms.presentation.vo.*;
import com.tuoheng.common.response.Result;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.List;
/**
* 飞行管理控制器Presentation层
* 提供单次飞行管理的 REST API 接口
* 负责飞行执行控制监控
*
* 职责
* 1. 接收前端的 VO 对象
* 2. VO 转换为 DTO 传递给 Application
* 3. Application 层返回的 DTO 转换为 VO 返回给前端
* 4. 不包含任何业务逻辑
*
* @author tuoheng
*/
@Slf4j
@RestController
@RequestMapping("/api/v1/flights")
@RequiredArgsConstructor
@Validated
@Tag(name = "飞行管理", description = "单次飞行管理相关接口")
public class FlightController {
private final FlightApplicationService flightApplicationService;
/**
* 执行单次飞行
* POST /api/v1/flights/execute
*/
@PostMapping("/execute")
@Operation(summary = "执行单次飞行", description = "执行一次飞行任务")
public Result<FlightRecordVO> executeFlight(@Valid @RequestBody FlightExecuteVO vo) {
log.info("接收到执行飞行请求: {}", vo);
FlightExecuteRequest request = FlightVoConverter.toExecuteRequest(vo);
FlightRecordResponse response = flightApplicationService.executeFlight(request);
FlightRecordVO result = FlightVoConverter.toVO(response);
return Result.success(result);
}
/**
* 紧急飞行一键起飞
* POST /api/v1/flights/emergency
*/
@PostMapping("/emergency")
@Operation(summary = "紧急飞行", description = "执行紧急飞行任务,动态生成航线")
public Result<FlightRecordVO> emergencyFlight(@Valid @RequestBody EmergencyFlightVO vo) {
log.info("接收到紧急飞行请求: {}", vo);
EmergencyFlightRequest request = FlightVoConverter.toEmergencyRequest(vo);
FlightRecordResponse response = flightApplicationService.emergencyFlight(request);
FlightRecordVO result = FlightVoConverter.toVO(response);
return Result.success(result);
}
/**
* 取消飞行
* POST /api/v1/flights/{recordId}/cancel
*/
@PostMapping("/{recordId}/cancel")
@Operation(summary = "取消飞行", description = "取消正在执行或准备中的飞行")
public Result<Void> cancelFlight(
@Parameter(description = "飞行记录ID") @PathVariable Long recordId,
@RequestParam(required = false) String reason) {
log.info("接收到取消飞行请求飞行记录ID: {}, 取消原因: {}", recordId, reason);
flightApplicationService.cancelFlight(recordId, reason);
return Result.success();
}
/**
* 暂停飞行
* POST /api/v1/flights/{recordId}/pause
*/
@PostMapping("/{recordId}/pause")
@Operation(summary = "暂停飞行", description = "暂停正在执行的飞行")
public Result<Void> pauseFlight(
@Parameter(description = "飞行记录ID") @PathVariable Long recordId) {
log.info("接收到暂停飞行请求飞行记录ID: {}", recordId);
flightApplicationService.pauseFlight(recordId);
return Result.success();
}
/**
* 恢复飞行
* POST /api/v1/flights/{recordId}/resume
*/
@PostMapping("/{recordId}/resume")
@Operation(summary = "恢复飞行", description = "恢复已暂停的飞行")
public Result<Void> resumeFlight(
@Parameter(description = "飞行记录ID") @PathVariable Long recordId) {
log.info("接收到恢复飞行请求飞行记录ID: {}", recordId);
flightApplicationService.resumeFlight(recordId);
return Result.success();
}
/**
* 返航
* POST /api/v1/flights/{recordId}/return-home
*/
@PostMapping("/{recordId}/return-home")
@Operation(summary = "返航", description = "命令无人机返回起飞点")
public Result<Void> returnHome(
@Parameter(description = "飞行记录ID") @PathVariable Long recordId) {
log.info("接收到返航请求飞行记录ID: {}", recordId);
flightApplicationService.returnHome(recordId);
return Result.success();
}
/**
* 无人机起飞
* POST /api/v1/flights/{recordId}/takeoff
*/
@PostMapping("/{recordId}/takeoff")
@Operation(summary = "无人机起飞", description = "命令无人机起飞")
public Result<Void> takeoff(
@Parameter(description = "飞行记录ID") @PathVariable Long recordId) {
log.info("接收到起飞请求飞行记录ID: {}", recordId);
flightApplicationService.takeoff(recordId);
return Result.success();
}
/**
* 无人机降落
* POST /api/v1/flights/{recordId}/land
*/
@PostMapping("/{recordId}/land")
@Operation(summary = "无人机降落", description = "命令无人机降落")
public Result<Void> land(
@Parameter(description = "飞行记录ID") @PathVariable Long recordId) {
log.info("接收到降落请求飞行记录ID: {}", recordId);
flightApplicationService.land(recordId);
return Result.success();
}
/**
* 调整飞行高度
* POST /api/v1/flights/{recordId}/altitude
*/
@PostMapping("/{recordId}/altitude")
@Operation(summary = "调整飞行高度", description = "调整无人机的飞行高度")
public Result<Void> adjustAltitude(
@Parameter(description = "飞行记录ID") @PathVariable Long recordId,
@Parameter(description = "目标高度(米)") @RequestParam Integer altitude) {
log.info("接收到调整飞行高度请求飞行记录ID: {}, 目标高度: {}米", recordId, altitude);
flightApplicationService.adjustAltitude(recordId, altitude);
return Result.success();
}
/**
* 调整飞行速度
* POST /api/v1/flights/{recordId}/speed
*/
@PostMapping("/{recordId}/speed")
@Operation(summary = "调整飞行速度", description = "调整无人机的飞行速度")
public Result<Void> adjustSpeed(
@Parameter(description = "飞行记录ID") @PathVariable Long recordId,
@Parameter(description = "目标速度(m/s)") @RequestParam Double speed) {
log.info("接收到调整飞行速度请求飞行记录ID: {}, 目标速度: {}m/s", recordId, speed);
flightApplicationService.adjustSpeed(recordId, speed);
return Result.success();
}
/**
* 跳转到指定航点
* POST /api/v1/flights/{recordId}/goto-waypoint
*/
@PostMapping("/{recordId}/goto-waypoint")
@Operation(summary = "跳转到指定航点", description = "命令无人机跳转到指定航点")
public Result<Void> gotoWaypoint(
@Parameter(description = "飞行记录ID") @PathVariable Long recordId,
@Parameter(description = "航点索引") @RequestParam Integer waypointIndex) {
log.info("接收到跳转航点请求飞行记录ID: {}, 航点索引: {}", recordId, waypointIndex);
flightApplicationService.gotoWaypoint(recordId, waypointIndex);
return Result.success();
}
/**
* 查询飞行记录详情
* GET /api/v1/flights/{recordId}
*/
@GetMapping("/{recordId}")
@Operation(summary = "查询飞行记录详情", description = "根据飞行记录ID查询详细信息")
public Result<FlightRecordVO> getFlightRecord(
@Parameter(description = "飞行记录ID") @PathVariable Long recordId) {
log.info("接收到查询飞行记录请求飞行记录ID: {}", recordId);
FlightRecordResponse response = flightApplicationService.getFlightRecord(recordId);
FlightRecordVO result = FlightVoConverter.toVO(response);
return Result.success(result);
}
/**
* 查询飞行实时状态
* GET /api/v1/flights/{recordId}/status
*/
@GetMapping("/{recordId}/status")
@Operation(summary = "查询飞行实时状态", description = "查询飞行的实时状态信息")
public Result<FlightStatusVO> getFlightStatus(
@Parameter(description = "飞行记录ID") @PathVariable Long recordId) {
log.info("接收到查询飞行状态请求飞行记录ID: {}", recordId);
FlightStatusResponse response = flightApplicationService.getFlightStatus(recordId);
FlightStatusVO result = FlightVoConverter.toStatusVO(response);
return Result.success(result);
}
/**
* 查询飞行轨迹
* GET /api/v1/flights/{recordId}/trajectory
*/
@GetMapping("/{recordId}/trajectory")
@Operation(summary = "查询飞行轨迹", description = "查询飞行的历史轨迹点")
public Result<List<TrajectoryPointVO>> getFlightTrajectory(
@Parameter(description = "飞行记录ID") @PathVariable Long recordId) {
log.info("接收到查询飞行轨迹请求飞行记录ID: {}", recordId);
List<TrajectoryPointResponse> responses = flightApplicationService.getFlightTrajectory(recordId);
List<TrajectoryPointVO> results = FlightVoConverter.toTrajectoryVOList(responses);
return Result.success(results);
}
/**
* 查询飞行记录列表
* GET /api/v1/flights
*/
@GetMapping
@Operation(summary = "查询飞行记录列表", description = "根据条件查询飞行记录列表")
public Result<List<FlightRecordVO>> queryFlightRecords(FlightRecordQueryVO vo) {
log.info("接收到查询飞行记录列表请求,查询条件: {}", vo);
FlightRecordQueryRequest request = FlightVoConverter.toQueryRequest(vo);
List<FlightRecordResponse> responses = flightApplicationService.queryFlightRecords(request);
List<FlightRecordVO> results = FlightVoConverter.toVOList(responses);
return Result.success(results);
}
/**
* 查询设备的飞行记录
* GET /api/v1/flights/device/{deviceId}
*/
@GetMapping("/device/{deviceId}")
@Operation(summary = "查询设备飞行记录", description = "查询指定设备的所有飞行记录")
public Result<List<FlightRecordVO>> getFlightRecordsByDevice(
@Parameter(description = "设备ID") @PathVariable Long deviceId) {
log.info("接收到查询设备飞行记录请求设备ID: {}", deviceId);
List<FlightRecordResponse> responses = flightApplicationService.getFlightRecordsByDevice(deviceId);
List<FlightRecordVO> results = FlightVoConverter.toVOList(responses);
return Result.success(results);
}
/**
* 查询正在执行的飞行列表
* GET /api/v1/flights/active
*/
@GetMapping("/active")
@Operation(summary = "查询正在执行的飞行", description = "查询所有正在执行的飞行任务")
public Result<List<FlightRecordVO>> getActiveFlights() {
log.info("接收到查询正在执行的飞行请求");
List<FlightRecordResponse> responses = flightApplicationService.getActiveFlights();
List<FlightRecordVO> results = FlightVoConverter.toVOList(responses);
return Result.success(results);
}
/**
* 查询飞行统计信息
* GET /api/v1/flights/statistics
*/
@GetMapping("/statistics")
@Operation(summary = "查询飞行统计", description = "查询飞行统计信息")
public Result<FlightStatisticsVO> getFlightStatistics(
@Parameter(description = "设备ID") @RequestParam(required = false) Long deviceId,
@Parameter(description = "开始日期") @RequestParam(required = false) String startDate,
@Parameter(description = "结束日期") @RequestParam(required = false) String endDate) {
log.info("接收到查询飞行统计请求设备ID: {}, 开始日期: {}, 结束日期: {}", deviceId, startDate, endDate);
FlightStatisticsResponse response = flightApplicationService.getFlightStatistics(deviceId, startDate, endDate);
FlightStatisticsVO result = FlightVoConverter.toStatisticsVO(response);
return Result.success(result);
}
}

View File

@ -0,0 +1,52 @@
package com.tuoheng.airport.fms.presentation.converter;
import com.tuoheng.airport.fms.application.dto.*;
import com.tuoheng.airport.fms.presentation.vo.*;
import java.util.List;
import java.util.stream.Collectors;
public class FlightVoConverter {
public static FlightExecuteRequest toExecuteRequest(FlightExecuteVO vo) {
// TODO: 实现转换逻辑
return new FlightExecuteRequest();
}
public static EmergencyFlightRequest toEmergencyRequest(EmergencyFlightVO vo) {
// TODO: 实现转换逻辑
return new EmergencyFlightRequest();
}
public static FlightRecordQueryRequest toQueryRequest(FlightRecordQueryVO vo) {
// TODO: 实现转换逻辑
return new FlightRecordQueryRequest();
}
public static FlightRecordVO toVO(FlightRecordResponse response) {
// TODO: 实现转换逻辑
return new FlightRecordVO();
}
public static List<FlightRecordVO> toVOList(List<FlightRecordResponse> responses) {
return responses.stream().map(FlightVoConverter::toVO).collect(Collectors.toList());
}
public static FlightStatusVO toStatusVO(FlightStatusResponse response) {
// TODO: 实现转换逻辑
return new FlightStatusVO();
}
public static TrajectoryPointVO toTrajectoryVO(TrajectoryPointResponse response) {
// TODO: 实现转换逻辑
return new TrajectoryPointVO();
}
public static List<TrajectoryPointVO> toTrajectoryVOList(List<TrajectoryPointResponse> responses) {
return responses.stream().map(FlightVoConverter::toTrajectoryVO).collect(Collectors.toList());
}
public static FlightStatisticsVO toStatisticsVO(FlightStatisticsResponse response) {
// TODO: 实现转换逻辑
return new FlightStatisticsVO();
}
}

View File

@ -0,0 +1,55 @@
package com.tuoheng.airport.fms.presentation.vo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotNull;
/**
* 紧急飞行 VO
*
* @author tuoheng
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class EmergencyFlightVO {
/**
* 起点纬度
*/
@NotNull(message = "起点纬度不能为空")
private Double startLatitude;
/**
* 起点经度
*/
@NotNull(message = "起点经度不能为空")
private Double startLongitude;
/**
* 终点纬度
*/
@NotNull(message = "终点纬度不能为空")
private Double endLatitude;
/**
* 终点经度
*/
@NotNull(message = "终点经度不能为空")
private Double endLongitude;
/**
* 飞行高度
*/
@NotNull(message = "飞行高度不能为空")
private Double altitude;
/**
* 设备ID可选
*/
private Long deviceId;
}

View File

@ -0,0 +1,42 @@
package com.tuoheng.airport.fms.presentation.vo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotNull;
/**
* 飞行执行 VO
*
* @author tuoheng
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class FlightExecuteVO {
/**
* 任务ID可选
*/
private Long taskId;
/**
* 航线ID
*/
@NotNull(message = "航线ID不能为空")
private Long airlineId;
/**
* 设备ID
*/
@NotNull(message = "设备ID不能为空")
private Long deviceId;
/**
* 计划开始时间
*/
private String planStartTime;
}

View File

@ -0,0 +1,48 @@
package com.tuoheng.airport.fms.presentation.vo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 飞行记录查询 VO
*
* @author tuoheng
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class FlightRecordQueryVO {
/**
* 飞行编号
*/
private String flightCode;
/**
* 任务ID
*/
private Long taskId;
/**
* 设备ID
*/
private Long deviceId;
/**
* 飞行状态
*/
private String status;
/**
* 开始日期
*/
private String startDate;
/**
* 结束日期
*/
private String endDate;
}

View File

@ -0,0 +1,8 @@
package com.tuoheng.airport.fms.presentation.vo;
import lombok.Data;
@Data
public class FlightRecordVO {
// TODO: 添加字段
}

View File

@ -0,0 +1,27 @@
package com.tuoheng.airport.fms.presentation.vo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 飞行统计 VO
*
* @author tuoheng
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class FlightStatisticsVO {
private Long totalFlights;
private Long successCount;
private Long failureCount;
private Long cancelledCount;
private Long totalDuration;
private Double totalDistance;
private Long averageDuration;
private Double successRate;
}

View File

@ -0,0 +1,32 @@
package com.tuoheng.airport.fms.presentation.vo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
/**
* 飞行状态 VO
*
* @author tuoheng
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class FlightStatusVO {
private Long flightRecordId;
private String status;
private Double latitude;
private Double longitude;
private Double altitude;
private Double speed;
private Integer batteryLevel;
private Integer currentWaypointIndex;
private Double flownDistance;
private Long flownDuration;
private LocalDateTime updateTime;
}

View File

@ -0,0 +1,29 @@
package com.tuoheng.airport.fms.presentation.vo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
/**
* 飞行轨迹点 VO
*
* @author tuoheng
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class TrajectoryPointVO {
private Long id;
private Long flightRecordId;
private Double latitude;
private Double longitude;
private Double altitude;
private Double speed;
private Integer batteryLevel;
private LocalDateTime recordTime;
}

View File

@ -0,0 +1,8 @@
package com.tuoheng.airport.media.application.dto;
import lombok.Data;
@Data
public class MediaAnalysisResultRequest {
// TODO: 添加字段
}

View File

@ -0,0 +1,8 @@
package com.tuoheng.airport.media.application.dto;
import lombok.Data;
@Data
public class MediaCreateFromDeviceRequest {
// TODO: 添加字段
}

View File

@ -0,0 +1,8 @@
package com.tuoheng.airport.media.application.dto;
import lombok.Data;
@Data
public class MediaInfoResponse {
// TODO: 添加字段
}

View File

@ -0,0 +1,8 @@
package com.tuoheng.airport.media.application.dto;
import lombok.Data;
@Data
public class MediaQueryRequest {
// TODO: 添加字段
}

View File

@ -0,0 +1,8 @@
package com.tuoheng.airport.media.application.dto;
import lombok.Data;
@Data
public class MediaStatisticsResponse {
// TODO: 添加字段
}

View File

@ -0,0 +1,9 @@
package com.tuoheng.airport.media.application.dto;
import lombok.Data;
@Data
public class MediaUpdateRequest {
private Long id;
// TODO: 添加字段
}

View File

@ -0,0 +1,196 @@
package com.tuoheng.airport.media.application.service;
import com.tuoheng.airport.media.application.dto.*;
import java.util.List;
/**
* 媒体资源应用服务接口Application层
* 定义媒体资源相关的用例Use Cases
* 协调领域模型完成媒体资源管理操作
*
* @author tuoheng
*/
public interface MediaApplicationService {
/**
* 查询媒体资源详情
*
* @param id 媒体资源ID
* @return 媒体资源响应
*/
MediaInfoResponse getMediaById(Long id);
/**
* 根据条件查询媒体资源列表
*
* @param request 查询请求
* @return 媒体资源列表
*/
List<MediaInfoResponse> queryMedia(MediaQueryRequest request);
/**
* 根据飞行记录ID查询媒体资源列表
* 业务逻辑
* 1. 验证飞行记录存在
* 2. 查询该飞行记录的所有媒体资源
* 3. 按拍摄时间排序
*
* @param flightRecordId 飞行记录ID
* @return 媒体资源列表
*/
List<MediaInfoResponse> getMediaByFlightRecordId(Long flightRecordId);
/**
* 根据设备ID查询媒体资源列表
*
* @param deviceId 设备ID
* @return 媒体资源列表
*/
List<MediaInfoResponse> getMediaByDeviceId(Long deviceId);
/**
* 更新媒体资源信息
* 业务逻辑
* 1. 验证媒体资源存在
* 2. 更新元数据信息标题描述标签等
* 3. 不允许修改文件本身
*
* @param request 更新请求
* @return 媒体资源响应
*/
MediaInfoResponse updateMedia(MediaUpdateRequest request);
/**
* 删除媒体资源
* 业务逻辑
* 1. 验证媒体资源存在
* 2. 检查是否被引用分析结果等
* 3. 从存储服务删除文件
* 4. 删除媒体资源元数据逻辑删除
*
* @param id 媒体资源ID
*/
void deleteMedia(Long id);
/**
* 批量删除媒体资源
* 业务逻辑
* 1. 验证所有媒体资源
* 2. 批量从存储服务删除
* 3. 批量删除元数据
*
* @param mediaIds 媒体资源ID列表
*/
void batchDeleteMedia(List<Long> mediaIds);
/**
* 下载媒体资源
* 业务逻辑
* 1. 验证媒体资源存在
* 2. 检查访问权限
* 3. 从存储服务获取文件
* 4. 记录下载日志
*
* @param id 媒体资源ID
*/
void downloadMedia(Long id);
/**
* 批量下载媒体资源ZIP压缩包
* 业务逻辑
* 1. 验证所有媒体资源
* 2. 从存储服务获取所有文件
* 3. 打包为ZIP文件
* 4. 返回ZIP文件流
*
* @param mediaIds 媒体资源ID列表
*/
void batchDownloadMedia(List<Long> mediaIds);
/**
* 获取媒体资源访问URL
* 业务逻辑
* 1. 验证媒体资源存在
* 2. 调用存储服务生成临时访问URL
* 3. 设置URL有效期
*
* @param id 媒体资源ID
* @param expireSeconds URL有效期
* @return 媒体资源访问URL
*/
String getMediaUrl(Long id, Long expireSeconds);
/**
* 标记媒体资源为已分析
* 业务逻辑
* 1. 验证媒体资源存在
* 2. 保存AI分析结果
* 3. 更新媒体资源状态为已分析
* 4. 触发后续处理告警统计等
*
* @param id 媒体资源ID
* @param analysisResult 分析结果
* @return 媒体资源响应
*/
MediaInfoResponse markMediaAsAnalyzed(Long id, MediaAnalysisResultRequest analysisResult);
/**
* 查询媒体资源统计信息
* 业务逻辑
* 1. 统计媒体资源总数
* 2. 按类型分组统计图片视频
* 3. 统计存储空间使用量
* 4. 统计分析完成率
*
* @param flightRecordId 飞行记录ID可选
* @param deviceId 设备ID可选
* @return 媒体统计信息
*/
MediaStatisticsResponse getMediaStatistics(Long flightRecordId, Long deviceId);
/**
* 同步媒体资源上传状态
* 业务逻辑
* 1. 查询存储服务中的文件状态
* 2. 更新媒体资源的上传状态
* 3. 处理上传失败的情况
*
* @param id 媒体资源ID
* @return 媒体资源响应
*/
MediaInfoResponse syncMediaUploadStatus(Long id);
/**
* 创建媒体资源记录由设备上报触发
* 业务逻辑
* 1. 接收设备上报的媒体信息
* 2. 创建媒体资源记录
* 3. 关联飞行记录
* 4. 触发文件上传任务
*
* @param request 创建请求
* @return 媒体资源响应
*/
MediaInfoResponse createMediaFromDevice(MediaCreateFromDeviceRequest request);
/**
* 批量创建媒体资源记录
*
* @param requests 创建请求列表
* @return 媒体资源列表
*/
List<MediaInfoResponse> batchCreateMediaFromDevice(List<MediaCreateFromDeviceRequest> requests);
/**
* 重新上传媒体资源
* 业务逻辑
* 1. 验证媒体资源存在
* 2. 检查上传状态为失败
* 3. 重新触发上传任务
*
* @param id 媒体资源ID
* @return 媒体资源响应
*/
MediaInfoResponse retryUploadMedia(Long id);
}

View File

@ -0,0 +1,50 @@
package com.tuoheng.airport.media.domain.model;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* 媒体分析结果领域模型
*
* @author tuoheng
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MediaAnalysisResult {
/**
* 是否发现异常
*/
private Boolean hasAbnormality;
/**
* 异常类型列表
*/
private List<String> abnormalityTypes;
/**
* 异常描述
*/
private String abnormalityDescription;
/**
* 置信度0-1
*/
private Double confidence;
/**
* AI模型版本
*/
private String modelVersion;
/**
* 分析耗时毫秒
*/
private Long analysisTime;
}

View File

@ -0,0 +1,24 @@
package com.tuoheng.airport.media.domain.model;
/**
* 媒体分析状态枚举
*
* @author tuoheng
*/
public enum MediaAnalysisStatus {
PENDING("待分析"),
ANALYZING("分析中"),
COMPLETED("分析完成"),
FAILED("分析失败");
private final String description;
MediaAnalysisStatus(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
}

View File

@ -0,0 +1,90 @@
package com.tuoheng.airport.media.domain.model;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
/**
* 媒体资源领域模型
*
* @author tuoheng
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MediaInfo {
private Long id;
private String mediaName;
private MediaType mediaType;
private String fileUrl;
private Long fileSize;
private MediaUploadStatus uploadStatus;
private Long flightRecordId;
private Long deviceId;
private String shootingLocation;
private LocalDateTime shootingTime;
private MediaAnalysisStatus analysisStatus;
private String analysisResult;
private Long tenantId;
private LocalDateTime createTime;
private LocalDateTime updateTime;
private Boolean deleted;
// ==================== 业务方法 ====================
public void markAsUploading() {
this.uploadStatus = MediaUploadStatus.UPLOADING;
this.updateTime = LocalDateTime.now();
}
public void markAsUploaded(String fileUrl) {
this.uploadStatus = MediaUploadStatus.COMPLETED;
this.fileUrl = fileUrl;
this.updateTime = LocalDateTime.now();
}
public void markAsUploadFailed() {
this.uploadStatus = MediaUploadStatus.FAILED;
this.updateTime = LocalDateTime.now();
}
public void markAsAnalyzed(String analysisResult) {
this.analysisStatus = MediaAnalysisStatus.COMPLETED;
this.analysisResult = analysisResult;
this.updateTime = LocalDateTime.now();
}
public boolean isUploaded() {
return this.uploadStatus == MediaUploadStatus.COMPLETED;
}
public boolean isAnalyzed() {
return this.analysisStatus == MediaAnalysisStatus.COMPLETED;
}
public void delete() {
this.deleted = true;
this.updateTime = LocalDateTime.now();
}
public static MediaInfo create(String mediaName, MediaType mediaType, Long flightRecordId, Long deviceId, Long tenantId) {
LocalDateTime now = LocalDateTime.now();
return MediaInfo.builder()
.mediaName(mediaName)
.mediaType(mediaType)
.uploadStatus(MediaUploadStatus.PENDING)
.analysisStatus(MediaAnalysisStatus.PENDING)
.flightRecordId(flightRecordId)
.deviceId(deviceId)
.tenantId(tenantId)
.createTime(now)
.updateTime(now)
.deleted(false)
.build();
}
}

View File

@ -0,0 +1,73 @@
package com.tuoheng.airport.media.domain.model;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 媒体统计领域模型
*
* @author tuoheng
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MediaStatistics {
/**
* 媒体总数
*/
private Long totalCount;
/**
* 图片数量
*/
private Long imageCount;
/**
* 视频数量
*/
private Long videoCount;
/**
* 已上传数量
*/
private Long uploadedCount;
/**
* 已分析数量
*/
private Long analyzedCount;
/**
* 发现异常数量
*/
private Long abnormalityCount;
/**
* 总存储空间字节
*/
private Long totalStorageSize;
/**
* 分析完成率
*/
public double getAnalysisCompletionRate() {
if (totalCount == null || totalCount == 0) {
return 0.0;
}
return (double) analyzedCount / totalCount * 100;
}
/**
* 异常率
*/
public double getAbnormalityRate() {
if (analyzedCount == null || analyzedCount == 0) {
return 0.0;
}
return (double) abnormalityCount / analyzedCount * 100;
}
}

View File

@ -0,0 +1,22 @@
package com.tuoheng.airport.media.domain.model;
/**
* 媒体类型枚举
*
* @author tuoheng
*/
public enum MediaType {
IMAGE("图片"),
VIDEO("视频");
private final String description;
MediaType(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
}

View File

@ -0,0 +1,24 @@
package com.tuoheng.airport.media.domain.model;
/**
* 媒体上传状态枚举
*
* @author tuoheng
*/
public enum MediaUploadStatus {
PENDING("待上传"),
UPLOADING("上传中"),
COMPLETED("上传完成"),
FAILED("上传失败");
private final String description;
MediaUploadStatus(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
}

View File

@ -0,0 +1,36 @@
package com.tuoheng.airport.media.domain.repository;
import com.tuoheng.airport.media.domain.model.MediaInfo;
import java.util.List;
import java.util.Optional;
/**
* 媒体资源仓储接口
*
* @author tuoheng
*/
public interface MediaInfoRepository {
MediaInfo save(MediaInfo mediaInfo);
Optional<MediaInfo> findById(Long id);
List<MediaInfo> findByFlightRecordId(Long flightRecordId);
List<MediaInfo> findByDeviceId(Long deviceId);
List<MediaInfo> findByUploadStatus(String uploadStatus);
List<MediaInfo> findByAnalysisStatus(String analysisStatus);
void delete(Long id);
void batchDelete(List<Long> ids);
long countByFlightRecordId(Long flightRecordId);
long countByDeviceId(Long deviceId);
long sumFileSizeByTenantId(Long tenantId);
}

View File

@ -0,0 +1,227 @@
package com.tuoheng.airport.media.domain.service;
import com.tuoheng.airport.media.domain.model.MediaInfo;
import com.tuoheng.airport.media.domain.model.MediaAnalysisResult;
import com.tuoheng.airport.media.domain.model.MediaStatistics;
import java.util.List;
/**
* 媒体资源领域服务接口Domain层
* 封装媒体资源相关的业务逻辑和业务规则
*
* Domain Service 的职责
* 1. 封装媒体资源管理的核心业务规则
* 2. 协调媒体资源和存储服务
* 3. 保证媒体资源数据的一致性
* 4. 不包含事务管理事务由 Application 层管理
*
* @author tuoheng
*/
public interface MediaDomainService {
/**
* 创建媒体资源记录
* 业务规则
* 1. 生成唯一的媒体资源ID
* 2. 验证文件类型图片/视频
* 3. 关联飞行记录和设备
* 4. 初始化上传状态为待上传
*
* @param mediaInfo 媒体资源领域模型
* @return 创建后的媒体资源
*/
MediaInfo createMediaInfo(MediaInfo mediaInfo);
/**
* 批量创建媒体资源记录
* 业务规则
* 1. 验证所有媒体资源
* 2. 批量创建记录
* 3. 关联同一飞行记录
*
* @param mediaInfos 媒体资源列表
* @return 创建后的媒体资源列表
*/
List<MediaInfo> batchCreateMediaInfo(List<MediaInfo> mediaInfos);
/**
* 更新媒体资源上传状态
* 业务规则
* 1. 验证状态转换合法性待上传->上传中->完成/失败
* 2. 记录上传时间
* 3. 上传完成后触发分析任务
*
* @param mediaId 媒体资源ID
* @param uploadStatus 上传状态
* @param fileUrl 文件URL上传完成时
* @return 更新后的媒体资源
*/
MediaInfo updateUploadStatus(Long mediaId, String uploadStatus, String fileUrl);
/**
* 标记媒体资源为已分析
* 业务规则
* 1. 验证媒体资源已上传完成
* 2. 保存AI分析结果
* 3. 更新分析状态
* 4. 如果发现异常触发告警
*
* @param mediaId 媒体资源ID
* @param analysisResult 分析结果
* @return 更新后的媒体资源
*/
MediaInfo markAsAnalyzed(Long mediaId, MediaAnalysisResult analysisResult);
/**
* 删除媒体资源
* 业务规则
* 1. 检查媒体资源是否被引用
* 2. 从存储服务删除文件
* 3. 逻辑删除媒体资源记录
* 4. 更新统计信息
*
* @param mediaId 媒体资源ID
*/
void deleteMediaInfo(Long mediaId);
/**
* 批量删除媒体资源
* 业务规则
* 1. 验证所有媒体资源可以删除
* 2. 批量从存储服务删除
* 3. 批量删除记录
*
* @param mediaIds 媒体资源ID列表
*/
void batchDeleteMediaInfo(List<Long> mediaIds);
/**
* 查询媒体资源详情
*
* @param mediaId 媒体资源ID
* @return 媒体资源领域模型
*/
MediaInfo getMediaInfoById(Long mediaId);
/**
* 根据飞行记录ID查询媒体资源列表
*
* @param flightRecordId 飞行记录ID
* @return 媒体资源列表
*/
List<MediaInfo> getMediaInfoByFlightRecordId(Long flightRecordId);
/**
* 根据设备ID查询媒体资源列表
*
* @param deviceId 设备ID
* @return 媒体资源列表
*/
List<MediaInfo> getMediaInfoByDeviceId(Long deviceId);
/**
* 生成媒体资源访问URL
* 业务规则
* 1. 验证媒体资源已上传完成
* 2. 调用存储服务生成临时URL
* 3. 设置URL有效期
*
* @param mediaId 媒体资源ID
* @param expireSeconds URL有效期
* @return 媒体资源访问URL
*/
String generateMediaUrl(Long mediaId, Long expireSeconds);
/**
* 检查媒体资源是否被引用
* 业务规则
* 1. 检查是否有分析结果引用
* 2. 检查是否有告警引用
* 3. 被引用的媒体资源不能删除
*
* @param mediaId 媒体资源ID
* @return 是否被引用
*/
boolean isMediaReferenced(Long mediaId);
/**
* 计算媒体资源统计信息
* 业务规则
* 1. 统计媒体资源总数
* 2. 按类型分组统计图片视频
* 3. 统计存储空间使用量
* 4. 统计分析完成率
*
* @param flightRecordId 飞行记录ID可选
* @param deviceId 设备ID可选
* @return 媒体统计信息
*/
MediaStatistics calculateStatistics(Long flightRecordId, Long deviceId);
/**
* 同步媒体资源上传状态
* 业务规则
* 1. 查询存储服务中的文件状态
* 2. 更新媒体资源的上传状态
* 3. 处理上传超时的情况
*
* @param mediaId 媒体资源ID
* @return 更新后的媒体资源
*/
MediaInfo syncUploadStatus(Long mediaId);
/**
* 重新上传媒体资源
* 业务规则
* 1. 验证上传状态为失败
* 2. 重置上传状态为待上传
* 3. 触发上传任务
*
* @param mediaId 媒体资源ID
* @return 更新后的媒体资源
*/
MediaInfo retryUpload(Long mediaId);
/**
* 验证媒体文件类型
* 业务规则
* 1. 检查文件扩展名
* 2. 支持的图片格式jpg, jpeg, png, bmp
* 3. 支持的视频格式mp4, avi, mov
*
* @param fileName 文件名
* @return 是否为有效的媒体文件类型
*/
boolean isValidMediaType(String fileName);
/**
* 获取媒体文件类型
* 根据文件扩展名判断是图片还是视频
*
* @param fileName 文件名
* @return 媒体类型IMAGE/VIDEO
*/
String getMediaType(String fileName);
/**
* 清理过期的未上传媒体资源
* 业务规则
* 1. 查找创建超过24小时但未上传的媒体资源
* 2. 批量删除这些记录
*
* @return 清理的媒体资源数量
*/
int cleanExpiredUnuploadedMedia();
/**
* 检查媒体资源上传是否超时
* 业务规则
* 1. 上传中状态超过30分钟视为超时
* 2. 超时后标记为上传失败
*
* @param mediaId 媒体资源ID
* @return 是否超时
*/
boolean isUploadTimeout(Long mediaId);
}

View File

@ -0,0 +1,221 @@
package com.tuoheng.airport.media.presentation.controller;
import com.tuoheng.airport.media.application.dto.*;
import com.tuoheng.airport.media.application.service.MediaApplicationService;
import com.tuoheng.airport.media.presentation.converter.MediaVoConverter;
import com.tuoheng.airport.media.presentation.vo.*;
import com.tuoheng.common.response.Result;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.List;
/**
* 媒体资源管理控制器Presentation层
* 提供媒体资源管理的 REST API 接口
* 负责图片视频等媒体文件的管理
*
* 职责
* 1. 接收前端的 VO 对象
* 2. VO 转换为 DTO 传递给 Application
* 3. Application 层返回的 DTO 转换为 VO 返回给前端
* 4. 不包含任何业务逻辑
*
* @author tuoheng
*/
@Slf4j
@RestController
@RequestMapping("/api/v1/media")
@RequiredArgsConstructor
@Validated
@Tag(name = "媒体资源管理", description = "媒体资源管理相关接口")
public class MediaController {
private final MediaApplicationService mediaApplicationService;
/**
* 查询媒体资源详情
* GET /api/v1/media/{id}
*/
@GetMapping("/{id}")
@Operation(summary = "查询媒体资源详情", description = "根据媒体资源ID查询详细信息")
public Result<MediaInfoVO> getMediaById(
@Parameter(description = "媒体资源ID") @PathVariable Long id) {
log.info("接收到查询媒体资源请求媒体资源ID: {}", id);
MediaInfoResponse response = mediaApplicationService.getMediaById(id);
MediaInfoVO result = MediaVoConverter.toVO(response);
return Result.success(result);
}
/**
* 查询媒体资源列表
* GET /api/v1/media
*/
@GetMapping
@Operation(summary = "查询媒体资源列表", description = "根据条件查询媒体资源列表")
public Result<List<MediaInfoVO>> queryMedia(MediaQueryVO vo) {
log.info("接收到查询媒体资源列表请求,查询条件: {}", vo);
MediaQueryRequest request = MediaVoConverter.toQueryRequest(vo);
List<MediaInfoResponse> responses = mediaApplicationService.queryMedia(request);
List<MediaInfoVO> results = MediaVoConverter.toVOList(responses);
return Result.success(results);
}
/**
* 根据飞行记录ID查询媒体资源列表
* GET /api/v1/media/flight/{flightRecordId}
*/
@GetMapping("/flight/{flightRecordId}")
@Operation(summary = "查询飞行媒体资源", description = "查询指定飞行记录的所有媒体资源")
public Result<List<MediaInfoVO>> getMediaByFlightRecordId(
@Parameter(description = "飞行记录ID") @PathVariable Long flightRecordId) {
log.info("接收到查询飞行媒体资源请求飞行记录ID: {}", flightRecordId);
List<MediaInfoResponse> responses = mediaApplicationService.getMediaByFlightRecordId(flightRecordId);
List<MediaInfoVO> results = MediaVoConverter.toVOList(responses);
return Result.success(results);
}
/**
* 根据设备ID查询媒体资源列表
* GET /api/v1/media/device/{deviceId}
*/
@GetMapping("/device/{deviceId}")
@Operation(summary = "查询设备媒体资源", description = "查询指定设备的所有媒体资源")
public Result<List<MediaInfoVO>> getMediaByDeviceId(
@Parameter(description = "设备ID") @PathVariable Long deviceId) {
log.info("接收到查询设备媒体资源请求设备ID: {}", deviceId);
List<MediaInfoResponse> responses = mediaApplicationService.getMediaByDeviceId(deviceId);
List<MediaInfoVO> results = MediaVoConverter.toVOList(responses);
return Result.success(results);
}
/**
* 更新媒体资源信息
* PUT /api/v1/media/{id}
*/
@PutMapping("/{id}")
@Operation(summary = "更新媒体资源信息", description = "更新媒体资源的元数据信息")
public Result<MediaInfoVO> updateMedia(
@Parameter(description = "媒体资源ID") @PathVariable Long id,
@Valid @RequestBody MediaUpdateVO vo) {
log.info("接收到更新媒体资源请求媒体资源ID: {}, 请求参数: {}", id, vo);
vo.setId(id);
MediaUpdateRequest request = MediaVoConverter.toUpdateRequest(vo);
MediaInfoResponse response = mediaApplicationService.updateMedia(request);
MediaInfoVO result = MediaVoConverter.toVO(response);
return Result.success(result);
}
/**
* 删除媒体资源
* DELETE /api/v1/media/{id}
*/
@DeleteMapping("/{id}")
@Operation(summary = "删除媒体资源", description = "删除指定的媒体资源")
public Result<Void> deleteMedia(
@Parameter(description = "媒体资源ID") @PathVariable Long id) {
log.info("接收到删除媒体资源请求媒体资源ID: {}", id);
mediaApplicationService.deleteMedia(id);
return Result.success();
}
/**
* 批量删除媒体资源
* POST /api/v1/media/batch-delete
*/
@PostMapping("/batch-delete")
@Operation(summary = "批量删除媒体资源", description = "批量删除多个媒体资源")
public Result<Void> batchDeleteMedia(@RequestBody List<Long> mediaIds) {
log.info("接收到批量删除媒体资源请求,媒体资源数量: {}", mediaIds.size());
mediaApplicationService.batchDeleteMedia(mediaIds);
return Result.success();
}
/**
* 下载媒体资源
* GET /api/v1/media/{id}/download
*/
@GetMapping("/{id}/download")
@Operation(summary = "下载媒体资源", description = "下载指定的媒体资源文件")
public void downloadMedia(
@Parameter(description = "媒体资源ID") @PathVariable Long id) {
log.info("接收到下载媒体资源请求媒体资源ID: {}", id);
mediaApplicationService.downloadMedia(id);
}
/**
* 批量下载媒体资源ZIP压缩包
* POST /api/v1/media/batch-download
*/
@PostMapping("/batch-download")
@Operation(summary = "批量下载媒体资源", description = "批量下载多个媒体资源打包为ZIP文件")
public void batchDownloadMedia(@RequestBody List<Long> mediaIds) {
log.info("接收到批量下载媒体资源请求,媒体资源数量: {}", mediaIds.size());
mediaApplicationService.batchDownloadMedia(mediaIds);
}
/**
* 获取媒体资源访问URL
* GET /api/v1/media/{id}/url
*/
@GetMapping("/{id}/url")
@Operation(summary = "获取媒体资源URL", description = "获取媒体资源的临时访问URL")
public Result<String> getMediaUrl(
@Parameter(description = "媒体资源ID") @PathVariable Long id,
@Parameter(description = "URL有效期") @RequestParam(defaultValue = "3600") Long expireSeconds) {
log.info("接收到获取媒体资源URL请求媒体资源ID: {}, 有效期: {}秒", id, expireSeconds);
String url = mediaApplicationService.getMediaUrl(id, expireSeconds);
return Result.success(url);
}
/**
* 标记媒体资源为已分析
* POST /api/v1/media/{id}/analyzed
*/
@PostMapping("/{id}/analyzed")
@Operation(summary = "标记媒体已分析", description = "标记媒体资源已完成AI分析")
public Result<MediaInfoVO> markMediaAsAnalyzed(
@Parameter(description = "媒体资源ID") @PathVariable Long id,
@Valid @RequestBody MediaAnalysisResultVO analysisResult) {
log.info("接收到标记媒体已分析请求媒体资源ID: {}", id);
MediaAnalysisResultRequest request = MediaVoConverter.toAnalysisResultRequest(analysisResult);
MediaInfoResponse response = mediaApplicationService.markMediaAsAnalyzed(id, request);
MediaInfoVO result = MediaVoConverter.toVO(response);
return Result.success(result);
}
/**
* 查询媒体资源统计信息
* GET /api/v1/media/statistics
*/
@GetMapping("/statistics")
@Operation(summary = "查询媒体统计", description = "查询媒体资源统计信息")
public Result<MediaStatisticsVO> getMediaStatistics(
@Parameter(description = "飞行记录ID") @RequestParam(required = false) Long flightRecordId,
@Parameter(description = "设备ID") @RequestParam(required = false) Long deviceId) {
log.info("接收到查询媒体统计请求飞行记录ID: {}, 设备ID: {}", flightRecordId, deviceId);
MediaStatisticsResponse response = mediaApplicationService.getMediaStatistics(flightRecordId, deviceId);
MediaStatisticsVO result = MediaVoConverter.toStatisticsVO(response);
return Result.success(result);
}
/**
* 同步媒体资源上传状态
* POST /api/v1/media/{id}/sync-status
*/
@PostMapping("/{id}/sync-status")
@Operation(summary = "同步上传状态", description = "同步媒体资源的上传状态")
public Result<MediaInfoVO> syncMediaUploadStatus(
@Parameter(description = "媒体资源ID") @PathVariable Long id) {
log.info("接收到同步媒体上传状态请求媒体资源ID: {}", id);
MediaInfoResponse response = mediaApplicationService.syncMediaUploadStatus(id);
MediaInfoVO result = MediaVoConverter.toVO(response);
return Result.success(result);
}
}

View File

@ -0,0 +1,43 @@
package com.tuoheng.airport.media.presentation.converter;
import com.tuoheng.airport.media.application.dto.*;
import com.tuoheng.airport.media.presentation.vo.*;
import java.util.List;
import java.util.stream.Collectors;
public class MediaVoConverter {
public static MediaQueryRequest toQueryRequest(MediaQueryVO vo) {
// TODO: 实现转换逻辑
return new MediaQueryRequest();
}
public static MediaUpdateRequest toUpdateRequest(MediaUpdateVO vo) {
// TODO: 实现转换逻辑
return new MediaUpdateRequest();
}
public static MediaInfoVO toVO(MediaInfoResponse response) {
// TODO: 实现转换逻辑
return new MediaInfoVO();
}
public static List<MediaInfoVO> toVOList(List<MediaInfoResponse> responses) {
return responses.stream().map(MediaVoConverter::toVO).collect(Collectors.toList());
}
public static MediaAnalysisResultRequest toAnalysisRequest(MediaAnalysisResultVO vo) {
// TODO: 实现转换逻辑
return new MediaAnalysisResultRequest();
}
public static MediaAnalysisResultRequest toAnalysisResultRequest(MediaAnalysisResultVO vo) {
// TODO: 实现转换逻辑
return new MediaAnalysisResultRequest();
}
public static MediaStatisticsVO toStatisticsVO(MediaStatisticsResponse response) {
// TODO: 实现转换逻辑
return new MediaStatisticsVO();
}
}

View File

@ -0,0 +1,8 @@
package com.tuoheng.airport.media.presentation.vo;
import lombok.Data;
@Data
public class MediaAnalysisResultVO {
// TODO: 添加字段
}

View File

@ -0,0 +1,8 @@
package com.tuoheng.airport.media.presentation.vo;
import lombok.Data;
@Data
public class MediaInfoVO {
// TODO: 添加字段
}

View File

@ -0,0 +1,8 @@
package com.tuoheng.airport.media.presentation.vo;
import lombok.Data;
@Data
public class MediaQueryVO {
// TODO: 添加字段
}

Some files were not shown because too many files have changed in this diff Show More