refactor: DDD架构完善+测试mock配置修复

- 完善Service接口DDD分层架构
- 修复EquipmentHealthServiceTest mock配置
- 修复WorkOrderServiceTest状态转换测试mock
- 修复MaintenanceTaskServiceTest mock配置
- 修复SystemType枚举测试(FIRE→FIRE_PROTECTION)
- 添加工单事件监听器测试
- 添加工单状态历史测试
- 添加维保到期提醒功能

Tests: 100 passed, 0 failures
This commit is contained in:
chiguyong 2026-05-19 14:33:34 +08:00
parent 34c51288db
commit 473bf5b81e
266 changed files with 9345 additions and 6107 deletions

View File

@ -11,8 +11,13 @@ import com.ether.pms.asset.service.*;
import com.ether.pms.common.ApiResponse; import com.ether.pms.common.ApiResponse;
import com.ether.pms.common.BusinessException; import com.ether.pms.common.BusinessException;
import com.ether.pms.common.ErrorCode; import com.ether.pms.common.ErrorCode;
import com.ether.pms.common.util.BatchOperationValidator;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
@ -21,13 +26,6 @@ import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
@RestController @RestController
@RequestMapping("/api/asset/equipment") @RequestMapping("/api/asset/equipment")
@RequiredArgsConstructor @RequiredArgsConstructor
@ -40,10 +38,10 @@ public class EquipmentController {
private final EquipmentEnergyService energyService; private final EquipmentEnergyService energyService;
private final EquipmentFireService fireService; private final EquipmentFireService fireService;
private static final Set<String> ALLOWED_CONTENT_TYPES = Set.of( private static final Set<String> ALLOWED_CONTENT_TYPES =
Set.of(
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"application/vnd.ms-excel" "application/vnd.ms-excel");
);
private static final Set<String> ALLOWED_EXTENSIONS = Set.of(".xlsx", ".xls"); private static final Set<String> ALLOWED_EXTENSIONS = Set.of(".xlsx", ".xls");
@ -62,7 +60,8 @@ public class EquipmentController {
} }
@PutMapping("/{id}") @PutMapping("/{id}")
public ApiResponse<Equipment> updateEquipment(@PathVariable UUID id, @Valid @RequestBody Equipment equipment) { public ApiResponse<Equipment> updateEquipment(
@PathVariable UUID id, @Valid @RequestBody Equipment equipment) {
return ApiResponse.success(equipmentService.updateEquipment(id, equipment)); return ApiResponse.success(equipmentService.updateEquipment(id, equipment));
} }
@ -79,7 +78,8 @@ public class EquipmentController {
} }
@PostMapping("/import") @PostMapping("/import")
public ApiResponse<Map<String, Object>> importEquipment(@RequestParam("file") MultipartFile file, @RequestParam UUID projectId) { public ApiResponse<Map<String, Object>> importEquipment(
@RequestParam("file") MultipartFile file, @RequestParam UUID projectId) {
validateExcelFile(file); validateExcelFile(file);
return ApiResponse.success(equipmentService.importFromExcel(file, projectId)); return ApiResponse.success(equipmentService.importFromExcel(file, projectId));
} }
@ -121,8 +121,7 @@ public class EquipmentController {
if (rowCount > MAX_EXCEL_ROWS) { if (rowCount > MAX_EXCEL_ROWS) {
throw new BusinessException( throw new BusinessException(
ErrorCode.FILE_004, ErrorCode.FILE_004,
String.format("Excel 行数不能超过 %d 行,当前 %d 行", MAX_EXCEL_ROWS, rowCount) String.format("Excel 行数不能超过 %d 行,当前 %d 行", MAX_EXCEL_ROWS, rowCount));
);
} }
} }
} catch (IOException e) { } catch (IOException e) {
@ -155,7 +154,8 @@ public class EquipmentController {
} }
@GetMapping("/by-ownership") @GetMapping("/by-ownership")
public ApiResponse<List<Equipment>> getEquipmentsByOwnership(@RequestParam OwnershipType ownership) { public ApiResponse<List<Equipment>> getEquipmentsByOwnership(
@RequestParam OwnershipType ownership) {
return ApiResponse.success(equipmentService.getEquipmentsByOwnership(ownership)); return ApiResponse.success(equipmentService.getEquipmentsByOwnership(ownership));
} }
@ -180,13 +180,12 @@ public class EquipmentController {
@GetMapping("/{id}/elevator") @GetMapping("/{id}/elevator")
public ApiResponse<EquipmentElevator> getElevator(@PathVariable UUID id) { public ApiResponse<EquipmentElevator> getElevator(@PathVariable UUID id) {
return ApiResponse.success( return ApiResponse.success(elevatorService.getByEquipmentId(id).orElse(null));
elevatorService.getByEquipmentId(id).orElse(null)
);
} }
@PutMapping("/{id}/elevator") @PutMapping("/{id}/elevator")
public ApiResponse<EquipmentElevator> updateElevator(@PathVariable UUID id, @Valid @RequestBody EquipmentElevator elevator) { public ApiResponse<EquipmentElevator> updateElevator(
@PathVariable UUID id, @Valid @RequestBody EquipmentElevator elevator) {
elevator.setEquipmentId(id); elevator.setEquipmentId(id);
return ApiResponse.success(elevatorService.saveOrUpdate(elevator)); return ApiResponse.success(elevatorService.saveOrUpdate(elevator));
} }
@ -195,13 +194,12 @@ public class EquipmentController {
@GetMapping("/{id}/hvac") @GetMapping("/{id}/hvac")
public ApiResponse<EquipmentHvac> getHvac(@PathVariable UUID id) { public ApiResponse<EquipmentHvac> getHvac(@PathVariable UUID id) {
return ApiResponse.success( return ApiResponse.success(hvacService.getByEquipmentId(id).orElse(null));
hvacService.getByEquipmentId(id).orElse(null)
);
} }
@PutMapping("/{id}/hvac") @PutMapping("/{id}/hvac")
public ApiResponse<EquipmentHvac> updateHvac(@PathVariable UUID id, @Valid @RequestBody EquipmentHvac hvac) { public ApiResponse<EquipmentHvac> updateHvac(
@PathVariable UUID id, @Valid @RequestBody EquipmentHvac hvac) {
hvac.setEquipmentId(id); hvac.setEquipmentId(id);
return ApiResponse.success(hvacService.saveOrUpdate(hvac)); return ApiResponse.success(hvacService.saveOrUpdate(hvac));
} }
@ -210,13 +208,12 @@ public class EquipmentController {
@GetMapping("/{id}/energy") @GetMapping("/{id}/energy")
public ApiResponse<EquipmentEnergy> getEnergy(@PathVariable UUID id) { public ApiResponse<EquipmentEnergy> getEnergy(@PathVariable UUID id) {
return ApiResponse.success( return ApiResponse.success(energyService.getByEquipmentId(id).orElse(null));
energyService.getByEquipmentId(id).orElse(null)
);
} }
@PutMapping("/{id}/energy") @PutMapping("/{id}/energy")
public ApiResponse<EquipmentEnergy> updateEnergy(@PathVariable UUID id, @Valid @RequestBody EquipmentEnergy energy) { public ApiResponse<EquipmentEnergy> updateEnergy(
@PathVariable UUID id, @Valid @RequestBody EquipmentEnergy energy) {
energy.setEquipmentId(id); energy.setEquipmentId(id);
return ApiResponse.success(energyService.saveOrUpdate(energy)); return ApiResponse.success(energyService.saveOrUpdate(energy));
} }
@ -225,13 +222,12 @@ public class EquipmentController {
@GetMapping("/{id}/fire") @GetMapping("/{id}/fire")
public ApiResponse<EquipmentFire> getFire(@PathVariable UUID id) { public ApiResponse<EquipmentFire> getFire(@PathVariable UUID id) {
return ApiResponse.success( return ApiResponse.success(fireService.getByEquipmentId(id).orElse(null));
fireService.getByEquipmentId(id).orElse(null)
);
} }
@PutMapping("/{id}/fire") @PutMapping("/{id}/fire")
public ApiResponse<EquipmentFire> updateFire(@PathVariable UUID id, @Valid @RequestBody EquipmentFire fire) { public ApiResponse<EquipmentFire> updateFire(
@PathVariable UUID id, @Valid @RequestBody EquipmentFire fire) {
fire.setEquipmentId(id); fire.setEquipmentId(id);
return ApiResponse.success(fireService.saveOrUpdate(fire)); return ApiResponse.success(fireService.saveOrUpdate(fire));
} }

View File

@ -6,15 +6,14 @@ import com.ether.pms.asset.service.EquipmentHealthService;
import com.ether.pms.common.ApiResponse; import com.ether.pms.common.ApiResponse;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import java.math.BigDecimal;
import java.util.List;
import java.util.UUID;
import lombok.Data; import lombok.Data;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import java.math.BigDecimal;
import java.util.List;
import java.util.UUID;
@RestController @RestController
@RequestMapping("/api/asset/equipment-health") @RequestMapping("/api/asset/equipment-health")
@RequiredArgsConstructor @RequiredArgsConstructor
@ -30,24 +29,29 @@ public class EquipmentHealthController {
} }
@GetMapping("/{equipmentId}/history") @GetMapping("/{equipmentId}/history")
public ApiResponse<List<EquipmentHealthScore>> getHealthHistory(@PathVariable UUID equipmentId) { public ApiResponse<List<EquipmentHealthScore>> getHealthHistory(
@PathVariable UUID equipmentId) {
List<EquipmentHealthScore> history = equipmentHealthService.getHealthHistory(equipmentId); List<EquipmentHealthScore> history = equipmentHealthService.getHealthHistory(equipmentId);
return ApiResponse.success("[Beta] 健康评分数据准确性待验证", history); return ApiResponse.success("[Beta] 健康评分数据准确性待验证", history);
} }
@PostMapping("/calculate") @PostMapping("/calculate")
public ApiResponse<EquipmentHealthScore> calculateHealthScore(@Valid @RequestBody CalculateHealthRequest request) { public ApiResponse<EquipmentHealthScore> calculateHealthScore(
EquipmentHealthScore score = equipmentHealthService.calculateHealthScore(request.getEquipmentId()); @Valid @RequestBody CalculateHealthRequest request) {
EquipmentHealthScore score =
equipmentHealthService.calculateHealthScore(request.getEquipmentId());
return ApiResponse.success("[Beta] 健康评分数据准确性待验证", score); return ApiResponse.success("[Beta] 健康评分数据准确性待验证", score);
} }
@PostMapping("/failure-history") @PostMapping("/failure-history")
public ApiResponse<EquipmentFailureHistory> recordFailure(@Valid @RequestBody EquipmentFailureHistory failure) { public ApiResponse<EquipmentFailureHistory> recordFailure(
@Valid @RequestBody EquipmentFailureHistory failure) {
return ApiResponse.success(equipmentHealthService.recordFailure(failure)); return ApiResponse.success(equipmentHealthService.recordFailure(failure));
} }
@GetMapping("/failure-history/{equipmentId}") @GetMapping("/failure-history/{equipmentId}")
public ApiResponse<List<EquipmentFailureHistory>> getFailureHistory(@PathVariable UUID equipmentId) { public ApiResponse<List<EquipmentFailureHistory>> getFailureHistory(
@PathVariable UUID equipmentId) {
return ApiResponse.success(equipmentHealthService.getFailureHistory(equipmentId)); return ApiResponse.success(equipmentHealthService.getFailureHistory(equipmentId));
} }

View File

@ -4,13 +4,12 @@ import com.ether.pms.asset.entity.OwnershipEntity;
import com.ether.pms.asset.repository.OwnershipEntityRepository; import com.ether.pms.asset.repository.OwnershipEntityRepository;
import com.ether.pms.common.ApiResponse; import com.ether.pms.common.ApiResponse;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import java.util.List;
import java.util.UUID;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.UUID;
@RestController @RestController
@RequestMapping("/api/asset/ownership-entity") @RequestMapping("/api/asset/ownership-entity")
@RequiredArgsConstructor @RequiredArgsConstructor
@ -27,13 +26,15 @@ public class OwnershipEntityController {
@GetMapping("/{id}") @GetMapping("/{id}")
public ApiResponse<OwnershipEntity> getById(@PathVariable UUID id) { public ApiResponse<OwnershipEntity> getById(@PathVariable UUID id) {
return ApiResponse.success( return ApiResponse.success(
ownershipEntityRepository.findByIdAndIsDeletedFalse(id).orElse(null) ownershipEntityRepository.findByIdAndIsDeletedFalse(id).orElse(null));
);
} }
@PutMapping("/{id}") @PutMapping("/{id}")
public ApiResponse<OwnershipEntity> update(@PathVariable UUID id, @Valid @RequestBody OwnershipEntity entity) { public ApiResponse<OwnershipEntity> update(
OwnershipEntity existing = ownershipEntityRepository.findByIdAndIsDeletedFalse(id) @PathVariable UUID id, @Valid @RequestBody OwnershipEntity entity) {
OwnershipEntity existing =
ownershipEntityRepository
.findByIdAndIsDeletedFalse(id)
.orElseThrow(() -> new RuntimeException("归属主体不存在: " + id)); .orElseThrow(() -> new RuntimeException("归属主体不存在: " + id));
existing.setEntityName(entity.getEntityName()); existing.setEntityName(entity.getEntityName());
existing.setContactPerson(entity.getContactPerson()); existing.setContactPerson(entity.getContactPerson());
@ -51,7 +52,9 @@ public class OwnershipEntityController {
@DeleteMapping("/{id}") @DeleteMapping("/{id}")
public ApiResponse<Void> delete(@PathVariable UUID id) { public ApiResponse<Void> delete(@PathVariable UUID id) {
OwnershipEntity entity = ownershipEntityRepository.findByIdAndIsDeletedFalse(id) OwnershipEntity entity =
ownershipEntityRepository
.findByIdAndIsDeletedFalse(id)
.orElseThrow(() -> new RuntimeException("归属主体不存在: " + id)); .orElseThrow(() -> new RuntimeException("归属主体不存在: " + id));
entity.setIsDeleted(true); entity.setIsDeleted(true);
ownershipEntityRepository.save(entity); ownershipEntityRepository.save(entity);
@ -59,8 +62,10 @@ public class OwnershipEntityController {
} }
@GetMapping("/by-type") @GetMapping("/by-type")
public ApiResponse<List<OwnershipEntity>> getByType(@RequestParam OwnershipEntity.EntityType type) { public ApiResponse<List<OwnershipEntity>> getByType(
return ApiResponse.success(ownershipEntityRepository.findByEntityTypeAndIsDeletedFalse(type)); @RequestParam OwnershipEntity.EntityType type) {
return ApiResponse.success(
ownershipEntityRepository.findByEntityTypeAndIsDeletedFalse(type));
} }
@GetMapping @GetMapping

View File

@ -2,10 +2,10 @@ package com.ether.pms.asset.dto;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.UUID; import java.util.UUID;
import lombok.Data;
@Data @Data
public class EquipmentCreateDTO { public class EquipmentCreateDTO {

View File

@ -1,8 +1,7 @@
package com.ether.pms.asset.dto; package com.ether.pms.asset.dto;
import lombok.Data;
import java.util.UUID; import java.util.UUID;
import lombok.Data;
@Data @Data
public class SpaceNodeDTO { public class SpaceNodeDTO {

View File

@ -1,10 +1,10 @@
package com.ether.pms.asset.dto; package com.ether.pms.asset.dto;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.List; import java.util.List;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)

View File

@ -5,19 +5,20 @@ import com.ether.pms.asset.enums.EquipmentType;
import com.ether.pms.asset.enums.OwnershipType; import com.ether.pms.asset.enums.OwnershipType;
import com.ether.pms.asset.enums.SystemType; import com.ether.pms.asset.enums.SystemType;
import jakarta.persistence.*; import jakarta.persistence.*;
import lombok.Data;
import org.hibernate.annotations.JdbcTypeCode;
import org.hibernate.type.SqlTypes;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import lombok.Data;
import org.hibernate.annotations.JdbcTypeCode;
import org.hibernate.type.SqlTypes;
@Entity @Entity
@Table(name = "asset_equipment", indexes = { @Table(
name = "asset_equipment",
indexes = {
@Index(name = "idx_equipment_project", columnList = "project_id"), @Index(name = "idx_equipment_project", columnList = "project_id"),
@Index(name = "idx_equipment_space", columnList = "space_node_id"), @Index(name = "idx_equipment_space", columnList = "space_node_id"),
@Index(name = "idx_equipment_type", columnList = "equipment_type"), @Index(name = "idx_equipment_type", columnList = "equipment_type"),
@ -27,7 +28,7 @@ import java.util.UUID;
@Index(name = "idx_eq_project_status", columnList = "project_id, status"), @Index(name = "idx_eq_project_status", columnList = "project_id, status"),
@Index(name = "idx_eq_project_type", columnList = "project_id, equipment_type"), @Index(name = "idx_eq_project_type", columnList = "project_id, equipment_type"),
@Index(name = "idx_eq_project_deleted", columnList = "project_id, is_deleted") @Index(name = "idx_eq_project_deleted", columnList = "project_id, is_deleted")
}) })
@Data @Data
public class Equipment { public class Equipment {

View File

@ -1,12 +1,11 @@
package com.ether.pms.asset.entity; package com.ether.pms.asset.entity;
import jakarta.persistence.*; import jakarta.persistence.*;
import lombok.Data;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.UUID; import java.util.UUID;
import lombok.Data;
@Entity @Entity
@Table(name = "asset_equipment_elevator") @Table(name = "asset_equipment_elevator")

View File

@ -1,12 +1,11 @@
package com.ether.pms.asset.entity; package com.ether.pms.asset.entity;
import jakarta.persistence.*; import jakarta.persistence.*;
import lombok.Data;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.UUID; import java.util.UUID;
import lombok.Data;
@Entity @Entity
@Table(name = "asset_equipment_energy") @Table(name = "asset_equipment_energy")

View File

@ -1,19 +1,20 @@
package com.ether.pms.asset.entity; package com.ether.pms.asset.entity;
import jakarta.persistence.*; import jakarta.persistence.*;
import lombok.Data;
import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.UUID; import java.util.UUID;
import lombok.Data;
@Entity @Entity
@Table(name = "ops_equipment_failure_history", indexes = { @Table(
name = "ops_equipment_failure_history",
indexes = {
@Index(name = "idx_failure_equipment", columnList = "equipment_id"), @Index(name = "idx_failure_equipment", columnList = "equipment_id"),
@Index(name = "idx_failure_time", columnList = "failure_time"), @Index(name = "idx_failure_time", columnList = "failure_time"),
@Index(name = "idx_failure_project", columnList = "project_id"), @Index(name = "idx_failure_project", columnList = "project_id"),
@Index(name = "idx_efh_project_time", columnList = "project_id, failure_time"), @Index(name = "idx_efh_project_time", columnList = "project_id, failure_time"),
@Index(name = "idx_efh_equipment_time", columnList = "equipment_id, failure_time DESC") @Index(name = "idx_efh_equipment_time", columnList = "equipment_id, failure_time DESC")
}) })
@Data @Data
public class EquipmentFailureHistory { public class EquipmentFailureHistory {

View File

@ -1,12 +1,11 @@
package com.ether.pms.asset.entity; package com.ether.pms.asset.entity;
import jakarta.persistence.*; import jakarta.persistence.*;
import lombok.Data;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.UUID; import java.util.UUID;
import lombok.Data;
@Entity @Entity
@Table(name = "asset_equipment_fire") @Table(name = "asset_equipment_fire")

View File

@ -1,21 +1,20 @@
package com.ether.pms.asset.entity; package com.ether.pms.asset.entity;
import jakarta.persistence.*; import jakarta.persistence.*;
import lombok.Data;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.UUID; import java.util.UUID;
import lombok.Data;
/** /** 此功能为Beta版本数据准确性待验证 */
* 此功能为Beta版本数据准确性待验证
*/
@Entity @Entity
@Table(name = "ops_equipment_health_score", indexes = { @Table(
name = "ops_equipment_health_score",
indexes = {
@Index(name = "idx_health_equipment", columnList = "equipment_id"), @Index(name = "idx_health_equipment", columnList = "equipment_id"),
@Index(name = "idx_health_calc_time", columnList = "calculated_at"), @Index(name = "idx_health_calc_time", columnList = "calculated_at"),
@Index(name = "idx_health_project", columnList = "project_id") @Index(name = "idx_health_project", columnList = "project_id")
}) })
@Data @Data
public class EquipmentHealthScore { public class EquipmentHealthScore {

View File

@ -1,12 +1,11 @@
package com.ether.pms.asset.entity; package com.ether.pms.asset.entity;
import jakarta.persistence.*; import jakarta.persistence.*;
import lombok.Data;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.UUID; import java.util.UUID;
import lombok.Data;
@Entity @Entity
@Table(name = "asset_equipment_hvac") @Table(name = "asset_equipment_hvac")

View File

@ -0,0 +1,71 @@
package com.ether.pms.asset.entity;
import jakarta.persistence.*;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.UUID;
import lombok.Data;
@Entity
@Table(
name = "mdm_maintenance_reminder",
indexes = {
@Index(name = "idx_reminder_equipment", columnList = "equipment_id"),
@Index(name = "idx_reminder_type_date", columnList = "reminder_type, contract_end_date")
})
@Data
public class MaintenanceReminder {
public static final String REMIND_7 = "REMIND_7";
public static final String REMIND_3 = "REMIND_3";
public static final String REMIND_1 = "REMIND_1";
public static final String EXPIRED = "EXPIRED";
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID id;
@Column(name = "equipment_id", nullable = false)
private UUID equipmentId;
@Column(name = "equipment_code", length = 50)
private String equipmentCode;
@Column(name = "maintenance_vendor", length = 200)
private String maintenanceVendor;
@Column(name = "vendor_contact", length = 100)
private String vendorContact;
@Column(name = "contract_end_date", nullable = false)
private LocalDate contractEndDate;
@Column(name = "reminder_type", nullable = false, length = 20)
private String reminderType;
@Column(name = "reminded_at")
private LocalDateTime remindedAt;
@Column(name = "is_active")
private Boolean isActive = true;
@Column(name = "created_at", nullable = false)
private LocalDateTime createdAt;
@Column(name = "updated_at", nullable = false)
private LocalDateTime updatedAt;
@PrePersist
public void prePersist() {
this.createdAt = LocalDateTime.now();
this.updatedAt = LocalDateTime.now();
if (this.isActive == null) {
this.isActive = true;
}
}
@PreUpdate
public void preUpdate() {
this.updatedAt = LocalDateTime.now();
}
}

View File

@ -1,18 +1,19 @@
package com.ether.pms.asset.entity; package com.ether.pms.asset.entity;
import jakarta.persistence.*; import jakarta.persistence.*;
import lombok.Data;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.UUID; import java.util.UUID;
import lombok.Data;
@Entity @Entity
@Table(name = "asset_ownership_entity", indexes = { @Table(
name = "asset_ownership_entity",
indexes = {
@Index(name = "idx_ownership_entity_type", columnList = "entity_type"), @Index(name = "idx_ownership_entity_type", columnList = "entity_type"),
@Index(name = "idx_ownership_entity_code", columnList = "entity_code") @Index(name = "idx_ownership_entity_code", columnList = "entity_code")
}) })
@Data @Data
public class OwnershipEntity { public class OwnershipEntity {

View File

@ -1,18 +1,14 @@
package com.ether.pms.asset.enums; package com.ether.pms.asset.enums;
/**
* 商业地产8大系统分类枚举
*/
public enum SystemType { public enum SystemType {
HVAC("暖通空调"), HVAC("暖通空调"),
FIRE("消防系统"), FIRE_PROTECTION("消防系统"),
ELEVATOR("电梯系统"), ELEVATOR("电梯系统"),
ELECTRICAL("电气系统"), ELECTRICAL("电气系统"),
PLUMBING("给排水"), PLUMBING("给排水"),
BAS("弱电智能化"), SECURITY("弱电智能化"),
KITCHEN("餐饮厨房"),
LANDSCAPE("景观"), LANDSCAPE("景观"),
OTHER("其他"); ENERGY_METER("能源计量");
private final String description; private final String description;

View File

@ -0,0 +1,39 @@
package com.ether.pms.asset.event;
import com.ether.pms.asset.entity.EquipmentFailureHistory;
import java.time.LocalDateTime;
import java.util.UUID;
import lombok.Getter;
import org.springframework.context.ApplicationEvent;
@Getter
public class EquipmentFailureRecordedEvent extends ApplicationEvent {
private final UUID equipmentId;
private final UUID failureHistoryId;
private final UUID projectId;
private final String equipmentCode;
private final String equipmentName;
private final String faultType;
private final String faultLevel;
private final String failureReason;
private final String failureDescription;
private final LocalDateTime occurredAt;
private final LocalDateTime createdAt;
public EquipmentFailureRecordedEvent(Object source, EquipmentFailureHistory failure) {
super(source);
this.equipmentId = failure.getEquipmentId();
this.failureHistoryId = failure.getId();
this.projectId = failure.getProjectId();
this.equipmentCode = failure.getEquipmentCode();
this.equipmentName = failure.getEquipmentName();
this.faultType = failure.getFailureType() != null ? failure.getFailureType().name() : null;
this.faultLevel =
failure.getFailureLevel() != null ? failure.getFailureLevel().name() : null;
this.failureReason = failure.getFailureReason();
this.failureDescription = failure.getFailureDescription();
this.occurredAt = failure.getFailureTime();
this.createdAt = failure.getCreatedAt();
}
}

View File

@ -1,11 +1,10 @@
package com.ether.pms.asset.repository; package com.ether.pms.asset.repository;
import com.ether.pms.asset.entity.EquipmentElevator; import com.ether.pms.asset.entity.EquipmentElevator;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository @Repository
public interface EquipmentElevatorRepository extends JpaRepository<EquipmentElevator, UUID> { public interface EquipmentElevatorRepository extends JpaRepository<EquipmentElevator, UUID> {

View File

@ -1,11 +1,10 @@
package com.ether.pms.asset.repository; package com.ether.pms.asset.repository;
import com.ether.pms.asset.entity.EquipmentEnergy; import com.ether.pms.asset.entity.EquipmentEnergy;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository @Repository
public interface EquipmentEnergyRepository extends JpaRepository<EquipmentEnergy, UUID> { public interface EquipmentEnergyRepository extends JpaRepository<EquipmentEnergy, UUID> {

View File

@ -1,28 +1,35 @@
package com.ether.pms.asset.repository; package com.ether.pms.asset.repository;
import com.ether.pms.asset.entity.EquipmentFailureHistory; import com.ether.pms.asset.entity.EquipmentFailureHistory;
import java.time.LocalDateTime;
import java.util.List;
import java.util.UUID;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query; import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param; import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import java.time.LocalDateTime;
import java.util.List;
import java.util.UUID;
@Repository @Repository
public interface EquipmentFailureHistoryRepository extends JpaRepository<EquipmentFailureHistory, UUID> { public interface EquipmentFailureHistoryRepository
extends JpaRepository<EquipmentFailureHistory, UUID> {
List<EquipmentFailureHistory> findByEquipmentIdOrderByFailureTimeDesc(UUID equipmentId); List<EquipmentFailureHistory> findByEquipmentIdOrderByFailureTimeDesc(UUID equipmentId);
List<EquipmentFailureHistory> findByProjectIdAndFailureTimeBetweenOrderByFailureTimeDesc( List<EquipmentFailureHistory> findByProjectIdAndFailureTimeBetweenOrderByFailureTimeDesc(
UUID projectId, LocalDateTime startTime, LocalDateTime endTime); UUID projectId, LocalDateTime startTime, LocalDateTime endTime);
@Query("SELECT f FROM EquipmentFailureHistory f WHERE f.equipmentId = :equipmentId AND f.failureTime >= :since ORDER BY f.failureTime DESC") @Query(
List<EquipmentFailureHistory> findByEquipmentIdSince(@Param("equipmentId") UUID equipmentId, @Param("since") LocalDateTime since); "SELECT f FROM EquipmentFailureHistory f WHERE f.equipmentId = :equipmentId AND f.failureTime >= :since ORDER BY f.failureTime DESC")
List<EquipmentFailureHistory> findByEquipmentIdSince(
@Param("equipmentId") UUID equipmentId, @Param("since") LocalDateTime since);
@Query("SELECT COUNT(f) FROM EquipmentFailureHistory f WHERE f.equipmentId = :equipmentId AND f.failureTime >= :since") @Query(
long countByEquipmentIdSince(@Param("equipmentId") UUID equipmentId, @Param("since") LocalDateTime since); "SELECT COUNT(f) FROM EquipmentFailureHistory f WHERE f.equipmentId = :equipmentId AND f.failureTime >= :since")
long countByEquipmentIdSince(
@Param("equipmentId") UUID equipmentId, @Param("since") LocalDateTime since);
@Query("SELECT f FROM EquipmentFailureHistory f WHERE f.equipmentId = :equipmentId AND f.repairEndTime IS NOT NULL AND f.failureTime >= :since ORDER BY f.failureTime DESC") @Query(
List<EquipmentFailureHistory> findRepairedFailuresByEquipmentIdSince(@Param("equipmentId") UUID equipmentId, @Param("since") LocalDateTime since); "SELECT f FROM EquipmentFailureHistory f WHERE f.equipmentId = :equipmentId AND f.repairEndTime IS NOT NULL AND f.failureTime >= :since ORDER BY f.failureTime DESC")
List<EquipmentFailureHistory> findRepairedFailuresByEquipmentIdSince(
@Param("equipmentId") UUID equipmentId, @Param("since") LocalDateTime since);
} }

View File

@ -1,11 +1,10 @@
package com.ether.pms.asset.repository; package com.ether.pms.asset.repository;
import com.ether.pms.asset.entity.EquipmentFire; import com.ether.pms.asset.entity.EquipmentFire;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository @Repository
public interface EquipmentFireRepository extends JpaRepository<EquipmentFire, UUID> { public interface EquipmentFireRepository extends JpaRepository<EquipmentFire, UUID> {

View File

@ -1,29 +1,37 @@
package com.ether.pms.asset.repository; package com.ether.pms.asset.repository;
import com.ether.pms.asset.entity.EquipmentHealthScore; import com.ether.pms.asset.entity.EquipmentHealthScore;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
@Repository @Repository
public interface EquipmentHealthScoreRepository extends JpaRepository<EquipmentHealthScore, UUID> { public interface EquipmentHealthScoreRepository extends JpaRepository<EquipmentHealthScore, UUID> {
List<EquipmentHealthScore> findByEquipmentIdOrderByCalculatedAtDesc(UUID equipmentId); List<EquipmentHealthScore> findByEquipmentIdOrderByCalculatedAtDesc(UUID equipmentId);
@Query("SELECT h FROM EquipmentHealthScore h WHERE h.equipmentId = :equipmentId ORDER BY h.calculatedAt DESC LIMIT 1") @Query(
"SELECT h FROM EquipmentHealthScore h WHERE h.equipmentId = :equipmentId ORDER BY h.calculatedAt DESC LIMIT 1")
Optional<EquipmentHealthScore> findLatestByEquipmentId(@Param("equipmentId") UUID equipmentId); Optional<EquipmentHealthScore> findLatestByEquipmentId(@Param("equipmentId") UUID equipmentId);
@Query("SELECT h FROM EquipmentHealthScore h WHERE h.equipmentId = :equipmentId AND h.calculatedAt >= :since ORDER BY h.calculatedAt DESC") @Query(
List<EquipmentHealthScore> findByEquipmentIdSince(@Param("equipmentId") UUID equipmentId, @Param("since") LocalDateTime since); "SELECT h FROM EquipmentHealthScore h WHERE h.equipmentId = :equipmentId AND h.calculatedAt >= :since ORDER BY h.calculatedAt DESC")
List<EquipmentHealthScore> findByEquipmentIdSince(
@Param("equipmentId") UUID equipmentId, @Param("since") LocalDateTime since);
@Query("SELECT h FROM EquipmentHealthScore h WHERE h.projectId = :projectId ORDER BY h.calculatedAt DESC") @Query(
List<EquipmentHealthScore> findByProjectIdOrderByCalculatedAtDesc(@Param("projectId") UUID projectId); "SELECT h FROM EquipmentHealthScore h WHERE h.projectId = :projectId ORDER BY h.calculatedAt DESC")
List<EquipmentHealthScore> findByProjectIdOrderByCalculatedAtDesc(
@Param("projectId") UUID projectId);
@Query("SELECT h FROM EquipmentHealthScore h WHERE h.projectId = :projectId AND h.healthLevel IN :levels ORDER BY h.healthScore ASC") @Query(
List<EquipmentHealthScore> findByProjectIdAndHealthLevelIn(@Param("projectId") UUID projectId, @Param("levels") List<EquipmentHealthScore.HealthLevel> levels); "SELECT h FROM EquipmentHealthScore h WHERE h.projectId = :projectId AND h.healthLevel IN :levels ORDER BY h.healthScore ASC")
List<EquipmentHealthScore> findByProjectIdAndHealthLevelIn(
@Param("projectId") UUID projectId,
@Param("levels") List<EquipmentHealthScore.HealthLevel> levels);
} }

View File

@ -1,11 +1,10 @@
package com.ether.pms.asset.repository; package com.ether.pms.asset.repository;
import com.ether.pms.asset.entity.EquipmentHvac; import com.ether.pms.asset.entity.EquipmentHvac;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository @Repository
public interface EquipmentHvacRepository extends JpaRepository<EquipmentHvac, UUID> { public interface EquipmentHvacRepository extends JpaRepository<EquipmentHvac, UUID> {

View File

@ -4,21 +4,22 @@ import com.ether.pms.asset.entity.Equipment;
import com.ether.pms.asset.enums.EquipmentStatus; import com.ether.pms.asset.enums.EquipmentStatus;
import com.ether.pms.asset.enums.EquipmentType; import com.ether.pms.asset.enums.EquipmentType;
import com.ether.pms.asset.enums.OwnershipType; import com.ether.pms.asset.enums.OwnershipType;
import java.time.LocalDate;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query; import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param; import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
@Repository @Repository
public interface EquipmentRepository extends JpaRepository<Equipment, UUID> { public interface EquipmentRepository extends JpaRepository<Equipment, UUID> {
List<Equipment> findByProjectIdAndIsDeletedFalse(UUID projectId); List<Equipment> findByProjectIdAndIsDeletedFalse(UUID projectId);
List<Equipment> findByProjectIdAndStatusAndIsDeletedFalse(UUID projectId, EquipmentStatus status); List<Equipment> findByProjectIdAndStatusAndIsDeletedFalse(
UUID projectId, EquipmentStatus status);
List<Equipment> findBySpaceNodeIdAndIsDeletedFalse(UUID spaceNodeId); List<Equipment> findBySpaceNodeIdAndIsDeletedFalse(UUID spaceNodeId);
@ -38,12 +39,20 @@ public interface EquipmentRepository extends JpaRepository<Equipment, UUID> {
@Query("SELECT e FROM Equipment e WHERE e.spaceNodeId = :spaceNodeId AND e.isDeleted = false") @Query("SELECT e FROM Equipment e WHERE e.spaceNodeId = :spaceNodeId AND e.isDeleted = false")
List<Equipment> findBySpaceNode(@Param("spaceNodeId") UUID spaceNodeId); List<Equipment> findBySpaceNode(@Param("spaceNodeId") UUID spaceNodeId);
@Query("SELECT COUNT(e) FROM Equipment e WHERE e.projectId = :projectId AND e.isDeleted = false") @Query(
"SELECT COUNT(e) FROM Equipment e WHERE e.projectId = :projectId AND e.isDeleted = false")
long countByProject(@Param("projectId") UUID projectId); long countByProject(@Param("projectId") UUID projectId);
@Query("SELECT e.equipmentType, COUNT(e) FROM Equipment e WHERE e.projectId = :projectId AND e.isDeleted = false GROUP BY e.equipmentType") @Query(
"SELECT e.equipmentType, COUNT(e) FROM Equipment e WHERE e.projectId = :projectId AND e.isDeleted = false GROUP BY e.equipmentType")
List<Object[]> countByType(@Param("projectId") UUID projectId); List<Object[]> countByType(@Param("projectId") UUID projectId);
@Query("SELECT e.ownershipType, COUNT(e) FROM Equipment e WHERE e.projectId = :projectId AND e.isDeleted = false GROUP BY e.ownershipType") @Query(
"SELECT e.ownershipType, COUNT(e) FROM Equipment e WHERE e.projectId = :projectId AND e.isDeleted = false GROUP BY e.ownershipType")
List<Object[]> countByOwnership(@Param("projectId") UUID projectId); List<Object[]> countByOwnership(@Param("projectId") UUID projectId);
@Query(
"SELECT e FROM Equipment e WHERE e.maintenanceContractEnd >= :startDate AND e.maintenanceContractEnd <= :endDate AND e.isDeleted = false AND e.maintenanceContractEnd IS NOT NULL")
List<Equipment> findByMaintenanceContractEndBetween(
@Param("startDate") LocalDate startDate, @Param("endDate") LocalDate endDate);
} }

View File

@ -0,0 +1,34 @@
package com.ether.pms.asset.repository;
import com.ether.pms.asset.entity.MaintenanceReminder;
import java.time.LocalDate;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
@Repository
public interface MaintenanceReminderRepository extends JpaRepository<MaintenanceReminder, UUID> {
Optional<MaintenanceReminder> findByEquipmentIdAndReminderTypeAndContractEndDate(
UUID equipmentId, String reminderType, LocalDate contractEndDate);
@Query(
"SELECT r FROM MaintenanceReminder r WHERE r.equipmentId = :equipmentId AND r.reminderType = :reminderType AND r.isActive = true")
List<MaintenanceReminder> findActiveByEquipmentIdAndReminderType(
@Param("equipmentId") UUID equipmentId, @Param("reminderType") String reminderType);
@Query(
"SELECT CASE WHEN COUNT(r) > 0 THEN true ELSE false END FROM MaintenanceReminder r WHERE r.equipmentId = :equipmentId AND r.reminderType = :reminderType")
boolean existsByEquipmentIdAndReminderType(
@Param("equipmentId") UUID equipmentId, @Param("reminderType") String reminderType);
List<MaintenanceReminder> findByEquipmentIdAndIsActiveTrue(UUID equipmentId);
@Query(
"SELECT r FROM MaintenanceReminder r WHERE r.contractEndDate <= :date AND r.isActive = true")
List<MaintenanceReminder> findExpiredReminders(@Param("date") LocalDate date);
}

View File

@ -1,12 +1,11 @@
package com.ether.pms.asset.repository; package com.ether.pms.asset.repository;
import com.ether.pms.asset.entity.OwnershipEntity; import com.ether.pms.asset.entity.OwnershipEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository @Repository
public interface OwnershipEntityRepository extends JpaRepository<OwnershipEntity, UUID> { public interface OwnershipEntityRepository extends JpaRepository<OwnershipEntity, UUID> {

View File

@ -0,0 +1,115 @@
package com.ether.pms.asset.scheduler;
import com.ether.pms.asset.entity.Equipment;
import com.ether.pms.asset.entity.MaintenanceReminder;
import com.ether.pms.asset.repository.EquipmentRepository;
import com.ether.pms.asset.repository.MaintenanceReminderRepository;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
@Slf4j
@Component
@RequiredArgsConstructor
public class MaintenanceReminderScheduler {
private final MaintenanceReminderRepository reminderRepository;
private final EquipmentRepository equipmentRepository;
@Scheduled(cron = "0 0 9 * * ?")
@Transactional
public void checkMaintenanceExpiry() {
log.info("开始执行维保到期提醒任务...");
try {
LocalDate today = LocalDate.now();
List<Equipment> expiringEquipments =
equipmentRepository.findByMaintenanceContractEndBetween(
today, today.plusDays(7));
int reminderCount = 0;
for (Equipment equipment : expiringEquipments) {
LocalDate endDate = equipment.getMaintenanceContractEnd();
String reminderType = determineReminderType(today, endDate);
if (!hasReminder(equipment.getId(), reminderType)) {
sendReminder(equipment, reminderType);
saveReminder(equipment, reminderType);
reminderCount++;
}
}
log.info("维保到期提醒任务完成,共发送 {} 条提醒", reminderCount);
} catch (Exception e) {
log.error("维保到期提醒任务失败: {}", e.getMessage(), e);
}
}
private String determineReminderType(LocalDate today, LocalDate endDate) {
long daysBetween = Duration.between(today.atStartOfDay(), endDate.atStartOfDay()).toDays();
if (daysBetween <= 0) {
return MaintenanceReminder.EXPIRED;
} else if (daysBetween <= 1) {
return MaintenanceReminder.REMIND_1;
} else if (daysBetween <= 3) {
return MaintenanceReminder.REMIND_3;
} else {
return MaintenanceReminder.REMIND_7;
}
}
private boolean hasReminder(java.util.UUID equipmentId, String reminderType) {
return reminderRepository.existsByEquipmentIdAndReminderType(equipmentId, reminderType);
}
private void sendReminder(Equipment equipment, String reminderType) {
String reminderDesc = getReminderDescription(reminderType);
log.info(
"维保到期提醒: 设备[{}]的维保合同将在{}到期,提醒类型: {}",
equipment.getEquipmentCode(),
equipment.getMaintenanceContractEnd(),
reminderDesc);
// TODO: 后续对接消息通知系统发送实际通知
// 可以通过以下方式发送
// 1. 邮件通知维保负责人
// 2. 站内消息通知
// 3. 短信通知
// 4. 企业微信/钉钉机器人通知
}
private String getReminderDescription(String reminderType) {
return switch (reminderType) {
case MaintenanceReminder.REMIND_7 -> "7天前提醒";
case MaintenanceReminder.REMIND_3 -> "3天前提醒";
case MaintenanceReminder.REMIND_1 -> "1天前提醒";
case MaintenanceReminder.EXPIRED -> "已到期";
default -> "未知提醒";
};
}
private void saveReminder(Equipment equipment, String reminderType) {
MaintenanceReminder reminder = new MaintenanceReminder();
reminder.setEquipmentId(equipment.getId());
reminder.setEquipmentCode(equipment.getEquipmentCode());
reminder.setMaintenanceVendor(equipment.getMaintenanceVendor());
reminder.setVendorContact(equipment.getMaintenanceVendorContact());
reminder.setContractEndDate(equipment.getMaintenanceContractEnd());
reminder.setReminderType(reminderType);
reminder.setRemindedAt(LocalDateTime.now());
reminder.setIsActive(true);
reminderRepository.save(reminder);
log.debug("保存维保提醒记录: 设备[{}], 类型[{}]", equipment.getEquipmentCode(), reminderType);
}
}

View File

@ -1,7 +1,6 @@
package com.ether.pms.asset.service; package com.ether.pms.asset.service;
import com.ether.pms.asset.entity.EquipmentElevator; import com.ether.pms.asset.entity.EquipmentElevator;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;

View File

@ -1,7 +1,6 @@
package com.ether.pms.asset.service; package com.ether.pms.asset.service;
import com.ether.pms.asset.entity.EquipmentEnergy; import com.ether.pms.asset.entity.EquipmentEnergy;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;

View File

@ -1,7 +1,6 @@
package com.ether.pms.asset.service; package com.ether.pms.asset.service;
import com.ether.pms.asset.entity.EquipmentFire; import com.ether.pms.asset.entity.EquipmentFire;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;

View File

@ -9,44 +9,26 @@ import java.util.UUID;
public interface EquipmentHealthService { public interface EquipmentHealthService {
/** /**
* 计算设备健康度 * 计算设备健康度 基础分 = 100 故障率扣分 = 故障次数 × 5近30天每故障扣5分 维保扣分 = (1 - 维保完成率) × 20 年龄扣分 = 投入使用年限 ×
* 基础分 = 100 * 2最高扣10分 健康度 = 基础分 - 故障率扣分 - 维保扣分 - 年龄扣分
* 故障率扣分 = 故障次数 × 5近30天每故障扣5分
* 维保扣分 = (1 - 维保完成率) × 20
* 年龄扣分 = 投入使用年限 × 2最高扣10分
* 健康度 = 基础分 - 故障率扣分 - 维保扣分 - 年龄扣分
*/ */
EquipmentHealthScore calculateHealthScore(UUID equipmentId); EquipmentHealthScore calculateHealthScore(UUID equipmentId);
/** /** 计算平均故障间隔时间 (MTBF) MTBF = 运行时间 / 故障次数 */
* 计算平均故障间隔时间 (MTBF)
* MTBF = 运行时间 / 故障次数
*/
BigDecimal calculateMTBF(UUID equipmentId, Integer days); BigDecimal calculateMTBF(UUID equipmentId, Integer days);
/** /** 计算平均修复时间 (MTTR) MTTR = 总修复时间 / 修复次数 */
* 计算平均修复时间 (MTTR)
* MTTR = 总修复时间 / 修复次数
*/
BigDecimal calculateMTTR(UUID equipmentId, Integer days); BigDecimal calculateMTTR(UUID equipmentId, Integer days);
/** /** 记录故障 */
* 记录故障
*/
EquipmentFailureHistory recordFailure(EquipmentFailureHistory failure); EquipmentFailureHistory recordFailure(EquipmentFailureHistory failure);
/** /** 获取设备健康度历史 */
* 获取设备健康度历史
*/
List<EquipmentHealthScore> getHealthHistory(UUID equipmentId); List<EquipmentHealthScore> getHealthHistory(UUID equipmentId);
/** /** 获取设备最新健康度 */
* 获取设备最新健康度
*/
EquipmentHealthScore getLatestHealthScore(UUID equipmentId); EquipmentHealthScore getLatestHealthScore(UUID equipmentId);
/** /** 获取设备的故障历史 */
* 获取设备的故障历史
*/
List<EquipmentFailureHistory> getFailureHistory(UUID equipmentId); List<EquipmentFailureHistory> getFailureHistory(UUID equipmentId);
} }

View File

@ -1,7 +1,6 @@
package com.ether.pms.asset.service; package com.ether.pms.asset.service;
import com.ether.pms.asset.entity.EquipmentHvac; import com.ether.pms.asset.entity.EquipmentHvac;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;

View File

@ -3,11 +3,10 @@ package com.ether.pms.asset.service;
import com.ether.pms.asset.entity.Equipment; import com.ether.pms.asset.entity.Equipment;
import com.ether.pms.asset.enums.EquipmentType; import com.ether.pms.asset.enums.EquipmentType;
import com.ether.pms.asset.enums.OwnershipType; import com.ether.pms.asset.enums.OwnershipType;
import org.springframework.web.multipart.MultipartFile;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import org.springframework.web.multipart.MultipartFile;
public interface EquipmentService { public interface EquipmentService {

View File

@ -3,13 +3,12 @@ package com.ether.pms.asset.service.impl;
import com.ether.pms.asset.entity.EquipmentElevator; import com.ether.pms.asset.entity.EquipmentElevator;
import com.ether.pms.asset.repository.EquipmentElevatorRepository; import com.ether.pms.asset.repository.EquipmentElevatorRepository;
import com.ether.pms.asset.service.EquipmentElevatorService; import com.ether.pms.asset.service.EquipmentElevatorService;
import java.util.Optional;
import java.util.UUID;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.util.Optional;
import java.util.UUID;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
public class EquipmentElevatorServiceImpl implements EquipmentElevatorService { public class EquipmentElevatorServiceImpl implements EquipmentElevatorService {
@ -19,7 +18,8 @@ public class EquipmentElevatorServiceImpl implements EquipmentElevatorService {
@Override @Override
@Transactional @Transactional
public EquipmentElevator saveOrUpdate(EquipmentElevator elevator) { public EquipmentElevator saveOrUpdate(EquipmentElevator elevator) {
Optional<EquipmentElevator> existing = elevatorRepository.findByEquipmentId(elevator.getEquipmentId()); Optional<EquipmentElevator> existing =
elevatorRepository.findByEquipmentId(elevator.getEquipmentId());
if (existing.isPresent()) { if (existing.isPresent()) {
elevator.setId(existing.get().getId()); elevator.setId(existing.get().getId());
} }

View File

@ -3,13 +3,12 @@ package com.ether.pms.asset.service.impl;
import com.ether.pms.asset.entity.EquipmentEnergy; import com.ether.pms.asset.entity.EquipmentEnergy;
import com.ether.pms.asset.repository.EquipmentEnergyRepository; import com.ether.pms.asset.repository.EquipmentEnergyRepository;
import com.ether.pms.asset.service.EquipmentEnergyService; import com.ether.pms.asset.service.EquipmentEnergyService;
import java.util.Optional;
import java.util.UUID;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.util.Optional;
import java.util.UUID;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
public class EquipmentEnergyServiceImpl implements EquipmentEnergyService { public class EquipmentEnergyServiceImpl implements EquipmentEnergyService {
@ -19,7 +18,8 @@ public class EquipmentEnergyServiceImpl implements EquipmentEnergyService {
@Override @Override
@Transactional @Transactional
public EquipmentEnergy saveOrUpdate(EquipmentEnergy energy) { public EquipmentEnergy saveOrUpdate(EquipmentEnergy energy) {
Optional<EquipmentEnergy> existing = energyRepository.findByEquipmentId(energy.getEquipmentId()); Optional<EquipmentEnergy> existing =
energyRepository.findByEquipmentId(energy.getEquipmentId());
if (existing.isPresent()) { if (existing.isPresent()) {
energy.setId(existing.get().getId()); energy.setId(existing.get().getId());
} }

View File

@ -3,13 +3,12 @@ package com.ether.pms.asset.service.impl;
import com.ether.pms.asset.entity.EquipmentFire; import com.ether.pms.asset.entity.EquipmentFire;
import com.ether.pms.asset.repository.EquipmentFireRepository; import com.ether.pms.asset.repository.EquipmentFireRepository;
import com.ether.pms.asset.service.EquipmentFireService; import com.ether.pms.asset.service.EquipmentFireService;
import java.util.Optional;
import java.util.UUID;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.util.Optional;
import java.util.UUID;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
public class EquipmentFireServiceImpl implements EquipmentFireService { public class EquipmentFireServiceImpl implements EquipmentFireService {

View File

@ -1,18 +1,15 @@
package com.ether.pms.asset.service.impl; package com.ether.pms.asset.service.impl;
import com.ether.pms.common.BusinessException;
import com.ether.pms.asset.entity.Equipment; import com.ether.pms.asset.entity.Equipment;
import com.ether.pms.asset.entity.EquipmentFailureHistory; import com.ether.pms.asset.entity.EquipmentFailureHistory;
import com.ether.pms.asset.entity.EquipmentHealthScore; import com.ether.pms.asset.entity.EquipmentHealthScore;
import com.ether.pms.asset.event.EquipmentFailureRecordedEvent;
import com.ether.pms.asset.repository.EquipmentFailureHistoryRepository; import com.ether.pms.asset.repository.EquipmentFailureHistoryRepository;
import com.ether.pms.asset.repository.EquipmentHealthScoreRepository; import com.ether.pms.asset.repository.EquipmentHealthScoreRepository;
import com.ether.pms.asset.repository.EquipmentRepository; import com.ether.pms.asset.repository.EquipmentRepository;
import com.ether.pms.asset.service.EquipmentHealthService; import com.ether.pms.asset.service.EquipmentHealthService;
import lombok.RequiredArgsConstructor; import com.ether.pms.common.BusinessException;
import lombok.extern.slf4j.Slf4j; import com.ether.pms.common.MaintenanceTaskRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.time.LocalDate; import java.time.LocalDate;
@ -21,6 +18,11 @@ import java.time.Period;
import java.time.temporal.ChronoUnit; import java.time.temporal.ChronoUnit;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Slf4j @Slf4j
@Service @Service
@ -30,8 +32,8 @@ public class EquipmentHealthServiceImpl implements EquipmentHealthService {
private final EquipmentFailureHistoryRepository failureHistoryRepository; private final EquipmentFailureHistoryRepository failureHistoryRepository;
private final EquipmentHealthScoreRepository healthScoreRepository; private final EquipmentHealthScoreRepository healthScoreRepository;
private final EquipmentRepository equipmentRepository; private final EquipmentRepository equipmentRepository;
// TODO: 需要改为从 ops 模块查询工单数据 private final MaintenanceTaskRepository maintenanceTaskRepository;
// private final MaintenanceTaskRepository maintenanceTaskRepository; private final ApplicationEventPublisher eventPublisher;
private static final BigDecimal BASE_SCORE = new BigDecimal("100"); private static final BigDecimal BASE_SCORE = new BigDecimal("100");
private static final BigDecimal FAILURE_DEDUCTION_PER_COUNT = new BigDecimal("5"); private static final BigDecimal FAILURE_DEDUCTION_PER_COUNT = new BigDecimal("5");
@ -43,20 +45,23 @@ public class EquipmentHealthServiceImpl implements EquipmentHealthService {
@Override @Override
@Transactional @Transactional
public EquipmentHealthScore calculateHealthScore(UUID equipmentId) { public EquipmentHealthScore calculateHealthScore(UUID equipmentId) {
Equipment equipment = equipmentRepository.findByIdAndIsDeletedFalse(equipmentId) Equipment equipment =
equipmentRepository
.findByIdAndIsDeletedFalse(equipmentId)
.orElseThrow(() -> new BusinessException(6001, "设备不存在")); .orElseThrow(() -> new BusinessException(6001, "设备不存在"));
// 获取近30天故障次数 // 获取近30天故障次数
LocalDateTime thirtyDaysAgo = LocalDateTime.now().minusDays(30); LocalDateTime thirtyDaysAgo = LocalDateTime.now().minusDays(30);
long failureCount30d = failureHistoryRepository.countByEquipmentIdSince(equipmentId, thirtyDaysAgo); long failureCount30d =
failureHistoryRepository.countByEquipmentIdSince(equipmentId, thirtyDaysAgo);
// 计算故障率扣分 // 计算故障率扣分
BigDecimal failureDeduction = FAILURE_DEDUCTION_PER_COUNT.multiply(BigDecimal.valueOf(failureCount30d)); BigDecimal failureDeduction =
FAILURE_DEDUCTION_PER_COUNT.multiply(BigDecimal.valueOf(failureCount30d));
// TODO: ops 模块查询工单数据计算维保完成率 // 计算维保完成率
// 暂时跳过维保完成率计算 BigDecimal maintenanceCompletionRate = calculateMaintenanceCompletionRate(equipmentId);
BigDecimal maintenanceCompletionRate = BigDecimal.ONE; BigDecimal maintenanceDeduction = calculateMaintenanceDeduction(maintenanceCompletionRate);
BigDecimal maintenanceDeduction = BigDecimal.ZERO;
// 计算年龄扣分 // 计算年龄扣分
BigDecimal equipmentAgeYears = calculateEquipmentAge(equipment); BigDecimal equipmentAgeYears = calculateEquipmentAge(equipment);
@ -66,7 +71,8 @@ public class EquipmentHealthServiceImpl implements EquipmentHealthService {
} }
// 计算健康度 // 计算健康度
BigDecimal healthScore = BASE_SCORE BigDecimal healthScore =
BASE_SCORE
.subtract(failureDeduction) .subtract(failureDeduction)
.subtract(maintenanceDeduction) .subtract(maintenanceDeduction)
.subtract(ageDeduction); .subtract(ageDeduction);
@ -90,7 +96,8 @@ public class EquipmentHealthServiceImpl implements EquipmentHealthService {
health.setMaintenanceDeduction(maintenanceDeduction.setScale(2, RoundingMode.HALF_UP)); health.setMaintenanceDeduction(maintenanceDeduction.setScale(2, RoundingMode.HALF_UP));
health.setAgeDeduction(ageDeduction.setScale(2, RoundingMode.HALF_UP)); health.setAgeDeduction(ageDeduction.setScale(2, RoundingMode.HALF_UP));
health.setFailureCount30d((int) failureCount30d); health.setFailureCount30d((int) failureCount30d);
health.setMaintenanceCompletionRate(maintenanceCompletionRate.setScale(4, RoundingMode.HALF_UP)); health.setMaintenanceCompletionRate(
maintenanceCompletionRate.setScale(4, RoundingMode.HALF_UP));
health.setEquipmentAgeYears(equipmentAgeYears.setScale(2, RoundingMode.HALF_UP)); health.setEquipmentAgeYears(equipmentAgeYears.setScale(2, RoundingMode.HALF_UP));
health.setMtbfHours(mtbf != null ? mtbf : BigDecimal.ZERO); health.setMtbfHours(mtbf != null ? mtbf : BigDecimal.ZERO);
health.setMttrHours(mttr != null ? mttr : BigDecimal.ZERO); health.setMttrHours(mttr != null ? mttr : BigDecimal.ZERO);
@ -108,7 +115,8 @@ public class EquipmentHealthServiceImpl implements EquipmentHealthService {
} }
LocalDateTime since = LocalDateTime.now().minusDays(days); LocalDateTime since = LocalDateTime.now().minusDays(days);
List<EquipmentFailureHistory> failures = failureHistoryRepository.findByEquipmentIdSince(equipmentId, since); List<EquipmentFailureHistory> failures =
failureHistoryRepository.findByEquipmentIdSince(equipmentId, since);
if (failures.isEmpty()) { if (failures.isEmpty()) {
return BigDecimal.ZERO; return BigDecimal.ZERO;
@ -126,7 +134,8 @@ public class EquipmentHealthServiceImpl implements EquipmentHealthService {
} }
// MTBF = 运行时间 / 故障次数 // MTBF = 运行时间 / 故障次数
BigDecimal mtbf = BigDecimal.valueOf(totalHours) BigDecimal mtbf =
BigDecimal.valueOf(totalHours)
.divide(BigDecimal.valueOf(failures.size()), 2, RoundingMode.HALF_UP); .divide(BigDecimal.valueOf(failures.size()), 2, RoundingMode.HALF_UP);
return mtbf; return mtbf;
@ -139,22 +148,27 @@ public class EquipmentHealthServiceImpl implements EquipmentHealthService {
} }
LocalDateTime since = LocalDateTime.now().minusDays(days); LocalDateTime since = LocalDateTime.now().minusDays(days);
List<EquipmentFailureHistory> repairedFailures = failureHistoryRepository List<EquipmentFailureHistory> repairedFailures =
.findRepairedFailuresByEquipmentIdSince(equipmentId, since); failureHistoryRepository.findRepairedFailuresByEquipmentIdSince(equipmentId, since);
if (repairedFailures.isEmpty()) { if (repairedFailures.isEmpty()) {
return BigDecimal.ZERO; return BigDecimal.ZERO;
} }
// 计算总修复时间 // 计算总修复时间
double totalRepairHours = repairedFailures.stream() double totalRepairHours =
repairedFailures.stream()
.filter(f -> f.getRepairDurationHours() != null) .filter(f -> f.getRepairDurationHours() != null)
.mapToDouble(EquipmentFailureHistory::getRepairDurationHours) .mapToDouble(EquipmentFailureHistory::getRepairDurationHours)
.sum(); .sum();
// MTTR = 总修复时间 / 修复次数 // MTTR = 总修复时间 / 修复次数
BigDecimal mttr = BigDecimal.valueOf(totalRepairHours) BigDecimal mttr =
.divide(BigDecimal.valueOf(repairedFailures.size()), 2, RoundingMode.HALF_UP); BigDecimal.valueOf(totalRepairHours)
.divide(
BigDecimal.valueOf(repairedFailures.size()),
2,
RoundingMode.HALF_UP);
return mttr; return mttr;
} }
@ -162,34 +176,44 @@ public class EquipmentHealthServiceImpl implements EquipmentHealthService {
@Override @Override
@Transactional @Transactional
public EquipmentFailureHistory recordFailure(EquipmentFailureHistory failure) { public EquipmentFailureHistory recordFailure(EquipmentFailureHistory failure) {
// 校验设备存在 Equipment equipment =
Equipment equipment = equipmentRepository.findByIdAndIsDeletedFalse(failure.getEquipmentId()) equipmentRepository
.findByIdAndIsDeletedFalse(failure.getEquipmentId())
.orElseThrow(() -> new BusinessException(6001, "设备不存在")); .orElseThrow(() -> new BusinessException(6001, "设备不存在"));
// 设置项目ID
if (failure.getProjectId() == null) { if (failure.getProjectId() == null) {
failure.setProjectId(equipment.getProjectId()); failure.setProjectId(equipment.getProjectId());
} }
// 设置设备信息
if (failure.getEquipmentName() == null) { if (failure.getEquipmentName() == null) {
failure.setEquipmentName(equipment.getEquipmentName()); failure.setEquipmentName(equipment.getEquipmentName());
} }
// 计算修复时长
if (failure.getRepairStartTime() != null && failure.getRepairEndTime() != null) { if (failure.getRepairStartTime() != null && failure.getRepairEndTime() != null) {
long minutes = ChronoUnit.MINUTES.between(failure.getRepairStartTime(), failure.getRepairEndTime()); long minutes =
ChronoUnit.MINUTES.between(
failure.getRepairStartTime(), failure.getRepairEndTime());
double hours = minutes / 60.0; double hours = minutes / 60.0;
failure.setRepairDurationHours(hours); failure.setRepairDurationHours(hours);
// 计算停机时长如果故障时间和修复开始时间不同 if (failure.getFailureTime() != null
if (failure.getFailureTime() != null && failure.getRepairStartTime().isAfter(failure.getFailureTime())) { && failure.getRepairStartTime().isAfter(failure.getFailureTime())) {
long downtimeMinutes = ChronoUnit.MINUTES.between(failure.getFailureTime(), failure.getRepairStartTime()); long downtimeMinutes =
ChronoUnit.MINUTES.between(
failure.getFailureTime(), failure.getRepairStartTime());
failure.setDowntimeHours(downtimeMinutes / 60.0); failure.setDowntimeHours(downtimeMinutes / 60.0);
} }
} }
return failureHistoryRepository.save(failure); EquipmentFailureHistory savedFailure = failureHistoryRepository.save(failure);
eventPublisher.publishEvent(new EquipmentFailureRecordedEvent(this, savedFailure));
log.info(
"发布设备故障记录事件: equipmentId={}, failureHistoryId={}",
savedFailure.getEquipmentId(),
savedFailure.getId());
return savedFailure;
} }
@Override @Override
@ -199,7 +223,8 @@ public class EquipmentHealthServiceImpl implements EquipmentHealthService {
@Override @Override
public EquipmentHealthScore getLatestHealthScore(UUID equipmentId) { public EquipmentHealthScore getLatestHealthScore(UUID equipmentId) {
return healthScoreRepository.findLatestByEquipmentId(equipmentId) return healthScoreRepository
.findLatestByEquipmentId(equipmentId)
.orElseThrow(() -> new BusinessException(6003, "没有健康度记录")); .orElseThrow(() -> new BusinessException(6003, "没有健康度记录"));
} }
@ -208,9 +233,7 @@ public class EquipmentHealthServiceImpl implements EquipmentHealthService {
return failureHistoryRepository.findByEquipmentIdOrderByFailureTimeDesc(equipmentId); return failureHistoryRepository.findByEquipmentIdOrderByFailureTimeDesc(equipmentId);
} }
/** /** 计算设备年龄(年) */
* 计算设备年龄
*/
private BigDecimal calculateEquipmentAge(Equipment equipment) { private BigDecimal calculateEquipmentAge(Equipment equipment) {
LocalDate installationDate = equipment.getInstallationDate(); LocalDate installationDate = equipment.getInstallationDate();
if (installationDate == null) { if (installationDate == null) {
@ -226,6 +249,35 @@ public class EquipmentHealthServiceImpl implements EquipmentHealthService {
int years = period.getYears(); int years = period.getYears();
int months = period.getMonths(); int months = period.getMonths();
return BigDecimal.valueOf(years).add(BigDecimal.valueOf(months).divide(BigDecimal.valueOf(12), 2, RoundingMode.HALF_UP)); return BigDecimal.valueOf(years)
.add(
BigDecimal.valueOf(months)
.divide(BigDecimal.valueOf(12), 2, RoundingMode.HALF_UP));
}
/** 计算维保完成率 */
private BigDecimal calculateMaintenanceCompletionRate(UUID equipmentId) {
List<MaintenanceTaskRepository.MaintenanceTaskInfo> tasks =
maintenanceTaskRepository.findByEquipmentId(equipmentId);
if (tasks.isEmpty()) {
return BigDecimal.ONE;
}
long completedCount =
tasks.stream()
.filter(
task ->
"COMPLETED".equals(task.status())
|| "VERIFIED".equals(task.status()))
.count();
return BigDecimal.valueOf(completedCount)
.divide(BigDecimal.valueOf(tasks.size()), 4, RoundingMode.HALF_UP);
}
/** 根据维保完成率计算扣分 */
private BigDecimal calculateMaintenanceDeduction(BigDecimal completionRate) {
return MAINTENANCE_DEDUCTION_FACTOR.multiply(BigDecimal.ONE.subtract(completionRate));
} }
} }

View File

@ -3,13 +3,12 @@ package com.ether.pms.asset.service.impl;
import com.ether.pms.asset.entity.EquipmentHvac; import com.ether.pms.asset.entity.EquipmentHvac;
import com.ether.pms.asset.repository.EquipmentHvacRepository; import com.ether.pms.asset.repository.EquipmentHvacRepository;
import com.ether.pms.asset.service.EquipmentHvacService; import com.ether.pms.asset.service.EquipmentHvacService;
import java.util.Optional;
import java.util.UUID;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.util.Optional;
import java.util.UUID;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
public class EquipmentHvacServiceImpl implements EquipmentHvacService { public class EquipmentHvacServiceImpl implements EquipmentHvacService {

View File

@ -1,7 +1,5 @@
package com.ether.pms.asset.service.impl; package com.ether.pms.asset.service.impl;
import com.ether.pms.common.BusinessException;
import com.ether.pms.common.ErrorCode;
import com.ether.pms.asset.entity.Equipment; import com.ether.pms.asset.entity.Equipment;
import com.ether.pms.asset.enums.EquipmentStatus; import com.ether.pms.asset.enums.EquipmentStatus;
import com.ether.pms.asset.enums.EquipmentType; import com.ether.pms.asset.enums.EquipmentType;
@ -9,6 +7,14 @@ import com.ether.pms.asset.enums.OwnershipType;
import com.ether.pms.asset.enums.SystemType; import com.ether.pms.asset.enums.SystemType;
import com.ether.pms.asset.repository.EquipmentRepository; import com.ether.pms.asset.repository.EquipmentRepository;
import com.ether.pms.asset.service.EquipmentService; import com.ether.pms.asset.service.EquipmentService;
import com.ether.pms.common.BusinessException;
import com.ether.pms.common.ErrorCode;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.apache.poi.ss.usermodel.*; import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.apache.poi.xssf.usermodel.XSSFWorkbook;
@ -16,14 +22,6 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
public class EquipmentServiceImpl implements EquipmentService { public class EquipmentServiceImpl implements EquipmentService {
@ -48,7 +46,9 @@ public class EquipmentServiceImpl implements EquipmentService {
@Override @Override
@Transactional @Transactional
public Equipment updateEquipment(UUID id, Equipment equipment) { public Equipment updateEquipment(UUID id, Equipment equipment) {
Equipment existing = equipmentRepository.findByIdAndIsDeletedFalse(id) Equipment existing =
equipmentRepository
.findByIdAndIsDeletedFalse(id)
.orElseThrow(() -> new BusinessException(ErrorCode.NOT_FOUND, "设备不存在")); .orElseThrow(() -> new BusinessException(ErrorCode.NOT_FOUND, "设备不存在"));
updateFields(existing, equipment); updateFields(existing, equipment);
return equipmentRepository.save(existing); return equipmentRepository.save(existing);
@ -57,7 +57,9 @@ public class EquipmentServiceImpl implements EquipmentService {
@Override @Override
@Transactional @Transactional
public void deleteEquipment(UUID id) { public void deleteEquipment(UUID id) {
Equipment equipment = equipmentRepository.findByIdAndIsDeletedFalse(id) Equipment equipment =
equipmentRepository
.findByIdAndIsDeletedFalse(id)
.orElseThrow(() -> new BusinessException(ErrorCode.NOT_FOUND, "设备不存在")); .orElseThrow(() -> new BusinessException(ErrorCode.NOT_FOUND, "设备不存在"));
equipment.setIsDeleted(true); equipment.setIsDeleted(true);
equipment.setStatus(EquipmentStatus.INACTIVE); equipment.setStatus(EquipmentStatus.INACTIVE);
@ -66,7 +68,8 @@ public class EquipmentServiceImpl implements EquipmentService {
@Override @Override
public Equipment getEquipmentById(UUID id) { public Equipment getEquipmentById(UUID id) {
return equipmentRepository.findByIdAndIsDeletedFalse(id) return equipmentRepository
.findByIdAndIsDeletedFalse(id)
.orElseThrow(() -> new BusinessException(ErrorCode.NOT_FOUND, "设备不存在")); .orElseThrow(() -> new BusinessException(ErrorCode.NOT_FOUND, "设备不存在"));
} }
@ -120,7 +123,9 @@ public class EquipmentServiceImpl implements EquipmentService {
} }
private String generateEquipmentCode() { private String generateEquipmentCode() {
return "EQC-" + LocalDateTime.now().format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMddHHmmss")); return "EQC-"
+ LocalDateTime.now()
.format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
} }
private void updateFields(Equipment existing, Equipment updated) { private void updateFields(Equipment existing, Equipment updated) {
@ -210,15 +215,31 @@ public class EquipmentServiceImpl implements EquipmentService {
@Override @Override
public byte[] exportToExcel(UUID projectId) { public byte[] exportToExcel(UUID projectId) {
List<Equipment> equipmentList = equipmentRepository.findByProjectIdAndIsDeletedFalse(projectId); List<Equipment> equipmentList =
equipmentRepository.findByProjectIdAndIsDeletedFalse(projectId);
try (Workbook workbook = new XSSFWorkbook(); try (Workbook workbook = new XSSFWorkbook();
ByteArrayOutputStream out = new ByteArrayOutputStream()) { ByteArrayOutputStream out = new ByteArrayOutputStream()) {
Sheet sheet = workbook.createSheet("设备列表"); Sheet sheet = workbook.createSheet("设备列表");
String[] headers = {"设备编码", "设备名称", "设备类型", "系统类型", "归属类型", String[] headers = {
"型号", "厂商", "额定功率(kW)", "额定电压(V)", "安装位置", "设备编码",
"维保商", "维保电话", "年检周期(月)", "购置日期", "购置价格", "保修到期"}; "设备名称",
"设备类型",
"系统类型",
"归属类型",
"型号",
"厂商",
"额定功率(kW)",
"额定电压(V)",
"安装位置",
"维保商",
"维保电话",
"年检周期(月)",
"购置日期",
"购置价格",
"保修到期"
};
Row headerRow = sheet.createRow(0); Row headerRow = sheet.createRow(0);
for (int i = 0; i < headers.length; i++) { for (int i = 0; i < headers.length; i++) {
Cell cell = headerRow.createCell(i); Cell cell = headerRow.createCell(i);
@ -229,22 +250,64 @@ public class EquipmentServiceImpl implements EquipmentService {
int rowNum = 1; int rowNum = 1;
for (Equipment eq : equipmentList) { for (Equipment eq : equipmentList) {
Row row = sheet.createRow(rowNum++); Row row = sheet.createRow(rowNum++);
row.createCell(0).setCellValue(eq.getEquipmentCode() != null ? eq.getEquipmentCode() : ""); row.createCell(0)
row.createCell(1).setCellValue(eq.getEquipmentName() != null ? eq.getEquipmentName() : ""); .setCellValue(eq.getEquipmentCode() != null ? eq.getEquipmentCode() : "");
row.createCell(2).setCellValue(eq.getEquipmentType() != null ? eq.getEquipmentType().getDescription() : ""); row.createCell(1)
row.createCell(3).setCellValue(eq.getSystemType() != null ? eq.getSystemType().getDescription() : ""); .setCellValue(eq.getEquipmentName() != null ? eq.getEquipmentName() : "");
row.createCell(4).setCellValue(eq.getOwnershipType() != null ? eq.getOwnershipType().getDescription() : ""); row.createCell(2)
.setCellValue(
eq.getEquipmentType() != null
? eq.getEquipmentType().getDescription()
: "");
row.createCell(3)
.setCellValue(
eq.getSystemType() != null
? eq.getSystemType().getDescription()
: "");
row.createCell(4)
.setCellValue(
eq.getOwnershipType() != null
? eq.getOwnershipType().getDescription()
: "");
row.createCell(5).setCellValue(eq.getModel() != null ? eq.getModel() : ""); row.createCell(5).setCellValue(eq.getModel() != null ? eq.getModel() : "");
row.createCell(6).setCellValue(eq.getManufacturer() != null ? eq.getManufacturer() : ""); row.createCell(6)
row.createCell(7).setCellValue(eq.getRatedPower() != null ? eq.getRatedPower().doubleValue() : 0); .setCellValue(eq.getManufacturer() != null ? eq.getManufacturer() : "");
row.createCell(8).setCellValue(eq.getRatedVoltage() != null ? eq.getRatedVoltage() : ""); row.createCell(7)
row.createCell(9).setCellValue(eq.getInstallationLocation() != null ? eq.getInstallationLocation() : ""); .setCellValue(
row.createCell(10).setCellValue(eq.getMaintenanceVendor() != null ? eq.getMaintenanceVendor() : ""); eq.getRatedPower() != null ? eq.getRatedPower().doubleValue() : 0);
row.createCell(11).setCellValue(eq.getMaintenanceVendorPhone() != null ? eq.getMaintenanceVendorPhone() : ""); row.createCell(8)
row.createCell(12).setCellValue(eq.getInspectionCycle() != null ? eq.getInspectionCycle() : 0); .setCellValue(eq.getRatedVoltage() != null ? eq.getRatedVoltage() : "");
row.createCell(13).setCellValue(eq.getPurchaseDate() != null ? eq.getPurchaseDate().toString() : ""); row.createCell(9)
row.createCell(14).setCellValue(eq.getPurchasePrice() != null ? eq.getPurchasePrice().doubleValue() : 0); .setCellValue(
row.createCell(15).setCellValue(eq.getWarrantyExpireDate() != null ? eq.getWarrantyExpireDate().toString() : ""); eq.getInstallationLocation() != null
? eq.getInstallationLocation()
: "");
row.createCell(10)
.setCellValue(
eq.getMaintenanceVendor() != null ? eq.getMaintenanceVendor() : "");
row.createCell(11)
.setCellValue(
eq.getMaintenanceVendorPhone() != null
? eq.getMaintenanceVendorPhone()
: "");
row.createCell(12)
.setCellValue(
eq.getInspectionCycle() != null ? eq.getInspectionCycle() : 0);
row.createCell(13)
.setCellValue(
eq.getPurchaseDate() != null
? eq.getPurchaseDate().toString()
: "");
row.createCell(14)
.setCellValue(
eq.getPurchasePrice() != null
? eq.getPurchasePrice().doubleValue()
: 0);
row.createCell(15)
.setCellValue(
eq.getWarrantyExpireDate() != null
? eq.getWarrantyExpireDate().toString()
: "");
} }
for (int i = 0; i < headers.length; i++) { for (int i = 0; i < headers.length; i++) {

View File

@ -3,5 +3,4 @@ package com.ether.pms.asset;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication @SpringBootApplication
public class TestApplication { public class TestApplication {}
}

View File

@ -1,10 +1,15 @@
package com.ether.pms.asset.repository; package com.ether.pms.asset.repository;
import static org.junit.jupiter.api.Assertions.*;
import com.ether.pms.asset.TestApplication; import com.ether.pms.asset.TestApplication;
import com.ether.pms.asset.entity.Equipment; import com.ether.pms.asset.entity.Equipment;
import com.ether.pms.asset.enums.EquipmentStatus; import com.ether.pms.asset.enums.EquipmentStatus;
import com.ether.pms.asset.enums.EquipmentType; import com.ether.pms.asset.enums.EquipmentType;
import com.ether.pms.asset.enums.OwnershipType; import com.ether.pms.asset.enums.OwnershipType;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -13,27 +18,19 @@ import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager; import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.ContextConfiguration;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import static org.junit.jupiter.api.Assertions.*;
/** /**
* EquipmentRepository 测试类 - TDD方式 * EquipmentRepository 测试类 - TDD方式
* *
* 使用 @DataJpaTest 切片测试只加载 JPA 相关组件 * <p>使用 @DataJpaTest 切片测试只加载 JPA 相关组件
*/ */
@DataJpaTest @DataJpaTest
@ContextConfiguration(classes = TestApplication.class) @ContextConfiguration(classes = TestApplication.class)
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class EquipmentRepositoryTest { class EquipmentRepositoryTest {
@Autowired @Autowired private TestEntityManager entityManager;
private TestEntityManager entityManager;
@Autowired @Autowired private EquipmentRepository equipmentRepository;
private EquipmentRepository equipmentRepository;
private UUID projectId; private UUID projectId;
private UUID spaceNodeId; private UUID spaceNodeId;
@ -82,7 +79,8 @@ class EquipmentRepositoryTest {
entityManager.persist(testEquipment); entityManager.persist(testEquipment);
entityManager.flush(); entityManager.flush();
Optional<Equipment> found = equipmentRepository.findByIdAndIsDeletedFalse(testEquipment.getId()); Optional<Equipment> found =
equipmentRepository.findByIdAndIsDeletedFalse(testEquipment.getId());
assertTrue(found.isEmpty()); assertTrue(found.isEmpty());
} }
@ -92,7 +90,8 @@ class EquipmentRepositoryTest {
entityManager.persist(testEquipment); entityManager.persist(testEquipment);
entityManager.flush(); entityManager.flush();
Optional<Equipment> found = equipmentRepository.findByIdAndIsDeletedFalse(testEquipment.getId()); Optional<Equipment> found =
equipmentRepository.findByIdAndIsDeletedFalse(testEquipment.getId());
assertTrue(found.isPresent()); assertTrue(found.isPresent());
assertFalse(found.get().getIsDeleted()); assertFalse(found.get().getIsDeleted());
@ -107,7 +106,8 @@ class EquipmentRepositoryTest {
equipmentRepository.save(testEquipment); equipmentRepository.save(testEquipment);
entityManager.flush(); entityManager.flush();
Optional<Equipment> found = equipmentRepository.findByIdAndIsDeletedFalse(testEquipment.getId()); Optional<Equipment> found =
equipmentRepository.findByIdAndIsDeletedFalse(testEquipment.getId());
assertTrue(found.isEmpty()); assertTrue(found.isEmpty());
} }
@ -130,7 +130,8 @@ class EquipmentRepositoryTest {
entityManager.persist(equipment); entityManager.persist(equipment);
entityManager.flush(); entityManager.flush();
List<Equipment> result = equipmentRepository.findByProjectIdAndIsDeletedFalse(UUID.randomUUID()); List<Equipment> result =
equipmentRepository.findByProjectIdAndIsDeletedFalse(UUID.randomUUID());
assertTrue(result.isEmpty()); assertTrue(result.isEmpty());
} }
@ -147,7 +148,8 @@ class EquipmentRepositoryTest {
entityManager.persist(hvac); entityManager.persist(hvac);
entityManager.flush(); entityManager.flush();
List<Equipment> elevators = equipmentRepository.findByEquipmentTypeAndIsDeletedFalse(EquipmentType.ELEVATOR); List<Equipment> elevators =
equipmentRepository.findByEquipmentTypeAndIsDeletedFalse(EquipmentType.ELEVATOR);
assertEquals(1, elevators.size()); assertEquals(1, elevators.size());
assertEquals(EquipmentType.ELEVATOR, elevators.get(0).getEquipmentType()); assertEquals(EquipmentType.ELEVATOR, elevators.get(0).getEquipmentType());
@ -165,7 +167,8 @@ class EquipmentRepositoryTest {
entityManager.persist(ownerOwned); entityManager.persist(ownerOwned);
entityManager.flush(); entityManager.flush();
List<Equipment> projectOwnedEquipments = equipmentRepository.findByOwnershipTypeAndIsDeletedFalse(OwnershipType.PROJECT); List<Equipment> projectOwnedEquipments =
equipmentRepository.findByOwnershipTypeAndIsDeletedFalse(OwnershipType.PROJECT);
assertEquals(1, projectOwnedEquipments.size()); assertEquals(1, projectOwnedEquipments.size());
assertEquals(OwnershipType.PROJECT, projectOwnedEquipments.get(0).getOwnershipType()); assertEquals(OwnershipType.PROJECT, projectOwnedEquipments.get(0).getOwnershipType());
@ -179,7 +182,8 @@ class EquipmentRepositoryTest {
entityManager.persist(equipment1); entityManager.persist(equipment1);
entityManager.flush(); entityManager.flush();
List<Equipment> result = equipmentRepository.findBySpaceNodeIdAndIsDeletedFalse(spaceNodeId); List<Equipment> result =
equipmentRepository.findBySpaceNodeIdAndIsDeletedFalse(spaceNodeId);
assertEquals(1, result.size()); assertEquals(1, result.size());
assertEquals(spaceNodeId, result.get(0).getSpaceNodeId()); assertEquals(spaceNodeId, result.get(0).getSpaceNodeId());

View File

@ -0,0 +1,160 @@
package com.ether.pms.asset.service;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
import com.ether.pms.asset.entity.EquipmentElevator;
import com.ether.pms.asset.repository.EquipmentElevatorRepository;
import com.ether.pms.asset.service.impl.EquipmentElevatorServiceImpl;
import java.math.BigDecimal;
import java.util.Optional;
import java.util.UUID;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
class EquipmentElevatorServiceTest {
@Mock private EquipmentElevatorRepository elevatorRepository;
@InjectMocks private EquipmentElevatorServiceImpl elevatorService;
private EquipmentElevator testElevator;
private UUID testId;
private UUID equipmentId;
@BeforeEach
void setUp() {
testId = UUID.randomUUID();
equipmentId = UUID.randomUUID();
testElevator = new EquipmentElevator();
testElevator.setId(testId);
testElevator.setEquipmentId(equipmentId);
testElevator.setElevatorType("乘客电梯");
testElevator.setElevatorModel("XYZ-1000");
testElevator.setLoadCapacity(1000);
testElevator.setSpeed(new BigDecimal("2.5"));
testElevator.setFloorCount(20);
testElevator.setRegistrationNo("REG-001");
testElevator.setMaintenanceLevel("A级");
}
@Test
void saveOrUpdate_shouldCreateNew_whenNotExists() {
when(elevatorRepository.findByEquipmentId(equipmentId)).thenReturn(Optional.empty());
when(elevatorRepository.save(any(EquipmentElevator.class)))
.thenAnswer(
invocation -> {
EquipmentElevator saved = invocation.getArgument(0);
saved.setId(testId);
return saved;
});
EquipmentElevator result = elevatorService.saveOrUpdate(testElevator);
assertNotNull(result);
assertEquals(equipmentId, result.getEquipmentId());
verify(elevatorRepository).save(testElevator);
}
@Test
void saveOrUpdate_shouldUpdateExisting_whenExists() {
EquipmentElevator existing = new EquipmentElevator();
existing.setId(testId);
existing.setEquipmentId(equipmentId);
EquipmentElevator updateData = new EquipmentElevator();
updateData.setEquipmentId(equipmentId);
updateData.setElevatorType("货梯");
updateData.setLoadCapacity(2000);
when(elevatorRepository.findByEquipmentId(equipmentId)).thenReturn(Optional.of(existing));
when(elevatorRepository.save(any(EquipmentElevator.class)))
.thenAnswer(invocation -> invocation.getArgument(0));
EquipmentElevator result = elevatorService.saveOrUpdate(updateData);
assertNotNull(result);
assertEquals(testId, result.getId());
assertEquals("货梯", result.getElevatorType());
assertEquals(2000, result.getLoadCapacity());
}
@Test
void getByEquipmentId_shouldReturnElevator_whenExists() {
when(elevatorRepository.findByEquipmentId(equipmentId))
.thenReturn(Optional.of(testElevator));
Optional<EquipmentElevator> result = elevatorService.getByEquipmentId(equipmentId);
assertTrue(result.isPresent());
assertEquals(testId, result.get().getId());
assertEquals("乘客电梯", result.get().getElevatorType());
}
@Test
void getByEquipmentId_shouldReturnEmpty_whenNotExists() {
UUID nonExistentId = UUID.randomUUID();
when(elevatorRepository.findByEquipmentId(nonExistentId)).thenReturn(Optional.empty());
Optional<EquipmentElevator> result = elevatorService.getByEquipmentId(nonExistentId);
assertFalse(result.isPresent());
}
@Test
void deleteByEquipmentId_shouldCallRepository() {
elevatorService.deleteByEquipmentId(equipmentId);
verify(elevatorRepository).deleteByEquipmentId(equipmentId);
}
@Test
void saveOrUpdate_shouldPreserveAllFields_whenUpdating() {
EquipmentElevator existing = new EquipmentElevator();
existing.setId(testId);
existing.setEquipmentId(equipmentId);
existing.setElevatorType("旧类型");
existing.setElevatorModel("旧型号");
EquipmentElevator updateData = new EquipmentElevator();
updateData.setEquipmentId(equipmentId);
updateData.setElevatorType("乘客电梯");
updateData.setElevatorModel("XYZ-1000");
updateData.setLoadCapacity(1000);
updateData.setSpeed(new BigDecimal("2.5"));
updateData.setFloorCount(20);
updateData.setShaftDimensions("2m x 2m");
updateData.setPitDepth(new BigDecimal("3.5"));
updateData.setOverheadHeight(new BigDecimal("4.0"));
updateData.setRegistrationNo("REG-001");
updateData.setInspectionCertificate("CERT-001");
updateData.setMaintenanceLevel("A级");
updateData.setRescuePlan("紧急救援计划内容");
when(elevatorRepository.findByEquipmentId(equipmentId)).thenReturn(Optional.of(existing));
when(elevatorRepository.save(any(EquipmentElevator.class)))
.thenAnswer(invocation -> invocation.getArgument(0));
EquipmentElevator result = elevatorService.saveOrUpdate(updateData);
assertEquals(testId, result.getId());
assertEquals("乘客电梯", result.getElevatorType());
assertEquals("XYZ-1000", result.getElevatorModel());
assertEquals(1000, result.getLoadCapacity());
assertEquals(new BigDecimal("2.5"), result.getSpeed());
assertEquals(20, result.getFloorCount());
assertEquals("2m x 2m", result.getShaftDimensions());
assertEquals(new BigDecimal("3.5"), result.getPitDepth());
assertEquals(new BigDecimal("4.0"), result.getOverheadHeight());
assertEquals("REG-001", result.getRegistrationNo());
assertEquals("CERT-001", result.getInspectionCertificate());
assertEquals("A级", result.getMaintenanceLevel());
assertEquals("紧急救援计划内容", result.getRescuePlan());
}
}

View File

@ -0,0 +1,379 @@
package com.ether.pms.asset.service;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;
import com.ether.pms.asset.entity.Equipment;
import com.ether.pms.asset.entity.EquipmentFailureHistory;
import com.ether.pms.asset.entity.EquipmentHealthScore;
import com.ether.pms.asset.enums.EquipmentStatus;
import com.ether.pms.asset.enums.EquipmentType;
import com.ether.pms.asset.enums.OwnershipType;
import com.ether.pms.asset.repository.EquipmentFailureHistoryRepository;
import com.ether.pms.asset.repository.EquipmentHealthScoreRepository;
import com.ether.pms.asset.repository.EquipmentRepository;
import com.ether.pms.asset.service.impl.EquipmentHealthServiceImpl;
import com.ether.pms.common.BusinessException;
import com.ether.pms.common.MaintenanceTaskRepository;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.context.ApplicationEventPublisher;
@ExtendWith(MockitoExtension.class)
class EquipmentHealthServiceTest {
@Mock private EquipmentFailureHistoryRepository failureHistoryRepository;
@Mock private EquipmentHealthScoreRepository healthScoreRepository;
@Mock private EquipmentRepository equipmentRepository;
@Mock private MaintenanceTaskRepository maintenanceTaskRepository;
@Mock private ApplicationEventPublisher eventPublisher;
@InjectMocks private EquipmentHealthServiceImpl healthService;
private Equipment testEquipment;
private UUID testId;
private UUID projectId;
@BeforeEach
void setUp() {
testId = UUID.randomUUID();
projectId = UUID.randomUUID();
testEquipment = new Equipment();
testEquipment.setId(testId);
testEquipment.setProjectId(projectId);
testEquipment.setEquipmentCode("EQ-001");
testEquipment.setEquipmentName("测试设备");
testEquipment.setEquipmentType(EquipmentType.HVAC);
testEquipment.setOwnershipType(OwnershipType.PROJECT);
testEquipment.setStatus(EquipmentStatus.ACTIVE);
testEquipment.setInstallationDate(LocalDate.now().minusYears(2));
}
@Test
void calculateHealthScore_shouldCalculateCorrectly_withNoFailures() {
when(equipmentRepository.findByIdAndIsDeletedFalse(testId))
.thenReturn(Optional.of(testEquipment));
when(maintenanceTaskRepository.findByEquipmentId(testId)).thenReturn(List.of());
when(failureHistoryRepository.countByEquipmentIdSince(eq(testId), any(LocalDateTime.class)))
.thenReturn(0L);
when(failureHistoryRepository.findByEquipmentIdSince(eq(testId), any(LocalDateTime.class)))
.thenReturn(List.of());
when(failureHistoryRepository.findRepairedFailuresByEquipmentIdSince(
eq(testId), any(LocalDateTime.class)))
.thenReturn(List.of());
when(healthScoreRepository.save(any(EquipmentHealthScore.class)))
.thenAnswer(
invocation -> {
EquipmentHealthScore score = invocation.getArgument(0);
score.setId(UUID.randomUUID());
return score;
});
EquipmentHealthScore result = healthService.calculateHealthScore(testId);
assertNotNull(result);
assertEquals(0, result.getFailureCount30d());
assertEquals(0, result.getFailureDeduction().intValue());
assertTrue(result.getHealthScore().compareTo(BigDecimal.ZERO) > 0);
}
@Test
void calculateHealthScore_shouldDeductForFailures() {
when(equipmentRepository.findByIdAndIsDeletedFalse(testId))
.thenReturn(Optional.of(testEquipment));
when(maintenanceTaskRepository.findByEquipmentId(testId)).thenReturn(List.of());
when(failureHistoryRepository.countByEquipmentIdSince(eq(testId), any(LocalDateTime.class)))
.thenReturn(3L);
when(failureHistoryRepository.findByEquipmentIdSince(eq(testId), any(LocalDateTime.class)))
.thenReturn(List.of());
when(failureHistoryRepository.findRepairedFailuresByEquipmentIdSince(
eq(testId), any(LocalDateTime.class)))
.thenReturn(List.of());
when(healthScoreRepository.save(any(EquipmentHealthScore.class)))
.thenAnswer(invocation -> invocation.getArgument(0));
EquipmentHealthScore result = healthService.calculateHealthScore(testId);
assertNotNull(result);
assertEquals(3, result.getFailureCount30d());
assertEquals(new BigDecimal("15.00"), result.getFailureDeduction());
}
@Test
void calculateHealthScore_shouldThrowException_whenEquipmentNotFound() {
UUID nonExistentId = UUID.randomUUID();
when(equipmentRepository.findByIdAndIsDeletedFalse(nonExistentId))
.thenReturn(Optional.empty());
BusinessException exception =
assertThrows(
BusinessException.class,
() -> healthService.calculateHealthScore(nonExistentId));
assertEquals(6001, exception.getCode());
}
@Test
void calculateMTBF_shouldReturnZero_whenNoFailures() {
when(failureHistoryRepository.findByEquipmentIdSince(eq(testId), any(LocalDateTime.class)))
.thenReturn(List.of());
BigDecimal result = healthService.calculateMTBF(testId, 30);
assertEquals(BigDecimal.ZERO, result);
}
@Test
void calculateMTBF_shouldCalculateCorrectly_withMultipleFailures() {
LocalDateTime now = LocalDateTime.now();
EquipmentFailureHistory failure1 = createFailure(testId, now.minusDays(5));
EquipmentFailureHistory failure2 = createFailure(testId, now.minusDays(15));
EquipmentFailureHistory failure3 = createFailure(testId, now.minusDays(25));
when(failureHistoryRepository.findByEquipmentIdSince(eq(testId), any(LocalDateTime.class)))
.thenReturn(Arrays.asList(failure1, failure2, failure3));
BigDecimal result = healthService.calculateMTBF(testId, 30);
assertNotNull(result);
assertTrue(result.compareTo(BigDecimal.ZERO) > 0);
}
@Test
void calculateMTBF_shouldUseDefaultDays_whenDaysIsNull() {
when(failureHistoryRepository.findByEquipmentIdSince(eq(testId), any(LocalDateTime.class)))
.thenReturn(List.of());
BigDecimal result = healthService.calculateMTBF(testId, null);
assertEquals(BigDecimal.ZERO, result);
}
@Test
void calculateMTBF_shouldUseDefaultDays_whenDaysIsNegative() {
when(failureHistoryRepository.findByEquipmentIdSince(eq(testId), any(LocalDateTime.class)))
.thenReturn(List.of());
BigDecimal result = healthService.calculateMTBF(testId, -5);
assertEquals(BigDecimal.ZERO, result);
}
@Test
void calculateMTTR_shouldReturnZero_whenNoRepairedFailures() {
when(failureHistoryRepository.findRepairedFailuresByEquipmentIdSince(
eq(testId), any(LocalDateTime.class)))
.thenReturn(List.of());
BigDecimal result = healthService.calculateMTTR(testId, 30);
assertEquals(BigDecimal.ZERO, result);
}
@Test
void calculateMTTR_shouldCalculateCorrectly_withRepairedFailures() {
EquipmentFailureHistory failure1 = createRepairedFailure(testId, 2.0);
EquipmentFailureHistory failure2 = createRepairedFailure(testId, 4.0);
when(failureHistoryRepository.findRepairedFailuresByEquipmentIdSince(
eq(testId), any(LocalDateTime.class)))
.thenReturn(Arrays.asList(failure1, failure2));
BigDecimal result = healthService.calculateMTTR(testId, 30);
assertNotNull(result);
assertEquals(new BigDecimal("3.00"), result);
}
@Test
void recordFailure_shouldSetProjectId_whenNull() {
EquipmentFailureHistory failure = new EquipmentFailureHistory();
failure.setEquipmentId(testId);
failure.setFailureTime(LocalDateTime.now());
failure.setFailureType(EquipmentFailureHistory.FailureType.SUDDEN);
when(equipmentRepository.findByIdAndIsDeletedFalse(testId))
.thenReturn(Optional.of(testEquipment));
doNothing().when(eventPublisher).publishEvent(any());
when(failureHistoryRepository.save(any(EquipmentFailureHistory.class)))
.thenAnswer(
invocation -> {
EquipmentFailureHistory saved = invocation.getArgument(0);
saved.setId(UUID.randomUUID());
return saved;
});
EquipmentFailureHistory result = healthService.recordFailure(failure);
assertNotNull(result);
assertEquals(projectId, result.getProjectId());
}
@Test
void recordFailure_shouldSetEquipmentName_whenNull() {
EquipmentFailureHistory failure = new EquipmentFailureHistory();
failure.setEquipmentId(testId);
failure.setFailureTime(LocalDateTime.now());
failure.setFailureType(EquipmentFailureHistory.FailureType.SUDDEN);
when(equipmentRepository.findByIdAndIsDeletedFalse(testId))
.thenReturn(Optional.of(testEquipment));
doNothing().when(eventPublisher).publishEvent(any());
when(failureHistoryRepository.save(any(EquipmentFailureHistory.class)))
.thenAnswer(invocation -> invocation.getArgument(0));
EquipmentFailureHistory result = healthService.recordFailure(failure);
assertEquals("测试设备", result.getEquipmentName());
}
@Test
void recordFailure_shouldCalculateRepairDuration() {
LocalDateTime repairStart = LocalDateTime.now().minusHours(2);
LocalDateTime repairEnd = LocalDateTime.now();
EquipmentFailureHistory failure = new EquipmentFailureHistory();
failure.setEquipmentId(testId);
failure.setFailureTime(LocalDateTime.now().minusHours(3));
failure.setRepairStartTime(repairStart);
failure.setRepairEndTime(repairEnd);
failure.setFailureType(EquipmentFailureHistory.FailureType.SUDDEN);
when(equipmentRepository.findByIdAndIsDeletedFalse(testId))
.thenReturn(Optional.of(testEquipment));
doNothing().when(eventPublisher).publishEvent(any());
when(failureHistoryRepository.save(any(EquipmentFailureHistory.class)))
.thenAnswer(invocation -> invocation.getArgument(0));
EquipmentFailureHistory result = healthService.recordFailure(failure);
assertNotNull(result.getRepairDurationHours());
assertTrue(result.getRepairDurationHours() >= 1.9);
}
@Test
void recordFailure_shouldCalculateDowntime() {
LocalDateTime failureTime = LocalDateTime.now().minusHours(1);
LocalDateTime repairStart = LocalDateTime.now();
EquipmentFailureHistory failure = new EquipmentFailureHistory();
failure.setEquipmentId(testId);
failure.setFailureTime(failureTime);
failure.setRepairStartTime(repairStart);
failure.setRepairEndTime(repairStart.plusHours(2));
failure.setFailureType(EquipmentFailureHistory.FailureType.SUDDEN);
when(equipmentRepository.findByIdAndIsDeletedFalse(testId))
.thenReturn(Optional.of(testEquipment));
doNothing().when(eventPublisher).publishEvent(any());
when(failureHistoryRepository.save(any(EquipmentFailureHistory.class)))
.thenAnswer(invocation -> invocation.getArgument(0));
EquipmentFailureHistory result = healthService.recordFailure(failure);
assertNotNull(result.getDowntimeHours());
assertTrue(result.getDowntimeHours() >= 0.9);
}
@Test
void recordFailure_shouldThrowException_whenEquipmentNotFound() {
UUID nonExistentId = UUID.randomUUID();
EquipmentFailureHistory failure = new EquipmentFailureHistory();
failure.setEquipmentId(nonExistentId);
when(equipmentRepository.findByIdAndIsDeletedFalse(nonExistentId))
.thenReturn(Optional.empty());
BusinessException exception =
assertThrows(BusinessException.class, () -> healthService.recordFailure(failure));
assertEquals(6001, exception.getCode());
}
@Test
void getHealthHistory_shouldReturnList() {
List<EquipmentHealthScore> history =
Arrays.asList(new EquipmentHealthScore(), new EquipmentHealthScore());
when(healthScoreRepository.findByEquipmentIdOrderByCalculatedAtDesc(testId))
.thenReturn(history);
List<EquipmentHealthScore> result = healthService.getHealthHistory(testId);
assertEquals(2, result.size());
}
@Test
void getLatestHealthScore_shouldReturnLatest() {
EquipmentHealthScore latest = new EquipmentHealthScore();
latest.setId(UUID.randomUUID());
latest.setHealthScore(new BigDecimal("85.00"));
when(healthScoreRepository.findLatestByEquipmentId(testId)).thenReturn(Optional.of(latest));
EquipmentHealthScore result = healthService.getLatestHealthScore(testId);
assertNotNull(result);
assertEquals(new BigDecimal("85.00"), result.getHealthScore());
}
@Test
void getLatestHealthScore_shouldThrowException_whenNoRecords() {
when(healthScoreRepository.findLatestByEquipmentId(testId)).thenReturn(Optional.empty());
BusinessException exception =
assertThrows(
BusinessException.class, () -> healthService.getLatestHealthScore(testId));
assertEquals(6003, exception.getCode());
}
@Test
void getFailureHistory_shouldReturnList() {
List<EquipmentFailureHistory> history =
Arrays.asList(new EquipmentFailureHistory(), new EquipmentFailureHistory());
when(failureHistoryRepository.findByEquipmentIdOrderByFailureTimeDesc(testId))
.thenReturn(history);
List<EquipmentFailureHistory> result = healthService.getFailureHistory(testId);
assertEquals(2, result.size());
}
private EquipmentFailureHistory createFailure(UUID equipmentId, LocalDateTime failureTime) {
EquipmentFailureHistory failure = new EquipmentFailureHistory();
failure.setId(UUID.randomUUID());
failure.setEquipmentId(equipmentId);
failure.setFailureTime(failureTime);
failure.setFailureType(EquipmentFailureHistory.FailureType.SUDDEN);
return failure;
}
private EquipmentFailureHistory createRepairedFailure(UUID equipmentId, double repairHours) {
EquipmentFailureHistory failure = new EquipmentFailureHistory();
failure.setId(UUID.randomUUID());
failure.setEquipmentId(equipmentId);
failure.setFailureTime(LocalDateTime.now().minusDays(1));
failure.setRepairStartTime(LocalDateTime.now().minusDays(1));
failure.setRepairEndTime(LocalDateTime.now());
failure.setRepairDurationHours(repairHours);
failure.setFailureType(EquipmentFailureHistory.FailureType.SUDDEN);
return failure;
}
}

View File

@ -0,0 +1,369 @@
package com.ether.pms.asset.service;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
import com.ether.pms.asset.entity.Equipment;
import com.ether.pms.asset.enums.EquipmentStatus;
import com.ether.pms.asset.enums.EquipmentType;
import com.ether.pms.asset.enums.OwnershipType;
import com.ether.pms.asset.enums.SystemType;
import com.ether.pms.asset.repository.EquipmentRepository;
import com.ether.pms.asset.service.impl.EquipmentServiceImpl;
import com.ether.pms.common.BusinessException;
import com.ether.pms.common.ErrorCode;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
class EquipmentServiceTest {
@Mock private EquipmentRepository equipmentRepository;
@InjectMocks private EquipmentServiceImpl equipmentService;
private Equipment testEquipment;
private UUID testId;
private UUID projectId;
@BeforeEach
void setUp() {
testId = UUID.randomUUID();
projectId = UUID.randomUUID();
testEquipment = new Equipment();
testEquipment.setId(testId);
testEquipment.setProjectId(projectId);
testEquipment.setEquipmentCode("EQ-001");
testEquipment.setEquipmentName("测试设备");
testEquipment.setEquipmentType(EquipmentType.HVAC);
testEquipment.setOwnershipType(OwnershipType.PROJECT);
testEquipment.setStatus(EquipmentStatus.ACTIVE);
}
@Test
void createEquipment_shouldGenerateCode_whenCodeIsNull() {
testEquipment.setEquipmentCode(null);
when(equipmentRepository.save(any(Equipment.class)))
.thenAnswer(
invocation -> {
Equipment saved = invocation.getArgument(0);
saved.setId(testId);
return saved;
});
Equipment result = equipmentService.createEquipment(testEquipment);
assertNotNull(result);
assertNotNull(result.getEquipmentCode());
assertTrue(result.getEquipmentCode().startsWith("EQC-"));
verify(equipmentRepository).save(any(Equipment.class));
}
@Test
void createEquipment_shouldSetDefaultStatus_whenStatusIsNull() {
testEquipment.setStatus(null);
when(equipmentRepository.save(any(Equipment.class)))
.thenAnswer(invocation -> invocation.getArgument(0));
Equipment result = equipmentService.createEquipment(testEquipment);
assertEquals(EquipmentStatus.ACTIVE, result.getStatus());
}
@Test
void createEquipment_shouldSetDefaultOwnershipType_whenOwnershipTypeIsNull() {
testEquipment.setOwnershipType(null);
when(equipmentRepository.save(any(Equipment.class)))
.thenAnswer(invocation -> invocation.getArgument(0));
Equipment result = equipmentService.createEquipment(testEquipment);
assertEquals(OwnershipType.PROJECT, result.getOwnershipType());
}
@Test
void createEquipment_shouldSaveEquipment_whenValid() {
when(equipmentRepository.save(any(Equipment.class))).thenReturn(testEquipment);
Equipment result = equipmentService.createEquipment(testEquipment);
assertNotNull(result);
assertEquals("EQ-001", result.getEquipmentCode());
verify(equipmentRepository).save(testEquipment);
}
@Test
void getEquipmentById_shouldReturnEquipment_whenExists() {
when(equipmentRepository.findByIdAndIsDeletedFalse(testId))
.thenReturn(Optional.of(testEquipment));
Equipment result = equipmentService.getEquipmentById(testId);
assertNotNull(result);
assertEquals(testId, result.getId());
assertEquals("EQ-001", result.getEquipmentCode());
}
@Test
void getEquipmentById_shouldThrowException_whenNotExists() {
UUID nonExistentId = UUID.randomUUID();
when(equipmentRepository.findByIdAndIsDeletedFalse(nonExistentId))
.thenReturn(Optional.empty());
BusinessException exception =
assertThrows(
BusinessException.class,
() -> equipmentService.getEquipmentById(nonExistentId));
assertEquals(ErrorCode.NOT_FOUND.getCode(), exception.getCode());
}
@Test
void updateEquipment_shouldUpdateFields_whenEquipmentExists() {
Equipment updateData = new Equipment();
updateData.setEquipmentName("更新后的设备名称");
updateData.setEquipmentType(EquipmentType.ELEVATOR);
updateData.setModel("NewModel");
updateData.setManufacturer("NewManufacturer");
when(equipmentRepository.findByIdAndIsDeletedFalse(testId))
.thenReturn(Optional.of(testEquipment));
when(equipmentRepository.save(any(Equipment.class)))
.thenAnswer(invocation -> invocation.getArgument(0));
Equipment result = equipmentService.updateEquipment(testId, updateData);
assertEquals("更新后的设备名称", result.getEquipmentName());
assertEquals(EquipmentType.ELEVATOR, result.getEquipmentType());
assertEquals("NewModel", result.getModel());
assertEquals("NewManufacturer", result.getManufacturer());
}
@Test
void updateEquipment_shouldThrowException_whenNotExists() {
UUID nonExistentId = UUID.randomUUID();
when(equipmentRepository.findByIdAndIsDeletedFalse(nonExistentId))
.thenReturn(Optional.empty());
assertThrows(
BusinessException.class,
() -> equipmentService.updateEquipment(nonExistentId, new Equipment()));
}
@Test
void deleteEquipment_shouldSetDeletedFlag_whenExists() {
when(equipmentRepository.findByIdAndIsDeletedFalse(testId))
.thenReturn(Optional.of(testEquipment));
when(equipmentRepository.save(any(Equipment.class)))
.thenAnswer(invocation -> invocation.getArgument(0));
equipmentService.deleteEquipment(testId);
assertTrue(testEquipment.getIsDeleted());
assertEquals(EquipmentStatus.INACTIVE, testEquipment.getStatus());
verify(equipmentRepository).save(testEquipment);
}
@Test
void deleteEquipment_shouldThrowException_whenNotExists() {
UUID nonExistentId = UUID.randomUUID();
when(equipmentRepository.findByIdAndIsDeletedFalse(nonExistentId))
.thenReturn(Optional.empty());
assertThrows(
BusinessException.class, () -> equipmentService.deleteEquipment(nonExistentId));
}
@Test
void getEquipmentsByProject_shouldReturnList() {
List<Equipment> equipmentList = Arrays.asList(testEquipment);
when(equipmentRepository.findByProjectIdAndIsDeletedFalse(projectId))
.thenReturn(equipmentList);
List<Equipment> result = equipmentService.getEquipmentsByProject(projectId);
assertEquals(1, result.size());
assertEquals(testEquipment, result.get(0));
}
@Test
void getEquipmentsBySpaceNode_shouldReturnList() {
UUID spaceNodeId = UUID.randomUUID();
testEquipment.setSpaceNodeId(spaceNodeId);
List<Equipment> equipmentList = Arrays.asList(testEquipment);
when(equipmentRepository.findBySpaceNodeIdAndIsDeletedFalse(spaceNodeId))
.thenReturn(equipmentList);
List<Equipment> result = equipmentService.getEquipmentsBySpaceNode(spaceNodeId);
assertEquals(1, result.size());
}
@Test
void getEquipmentsByType_shouldReturnList() {
List<Equipment> equipmentList = Arrays.asList(testEquipment);
when(equipmentRepository.findByEquipmentTypeAndIsDeletedFalse(EquipmentType.HVAC))
.thenReturn(equipmentList);
List<Equipment> result = equipmentService.getEquipmentsByType(EquipmentType.HVAC);
assertEquals(1, result.size());
}
@Test
void getEquipmentsByOwnership_shouldReturnList() {
List<Equipment> equipmentList = Arrays.asList(testEquipment);
when(equipmentRepository.findByOwnershipTypeAndIsDeletedFalse(OwnershipType.PROJECT))
.thenReturn(equipmentList);
List<Equipment> result = equipmentService.getEquipmentsByOwnership(OwnershipType.PROJECT);
assertEquals(1, result.size());
}
@Test
void getEquipmentStatsByType_shouldReturnStatsMap() {
Object[] stat1 = new Object[] {EquipmentType.HVAC, 5L};
Object[] stat2 = new Object[] {EquipmentType.ELEVATOR, 3L};
when(equipmentRepository.countByType(projectId)).thenReturn(Arrays.asList(stat1, stat2));
Map<String, Long> result = equipmentService.getEquipmentStatsByType(projectId);
assertEquals(2, result.size());
assertEquals(5L, result.get("HVAC"));
assertEquals(3L, result.get("ELEVATOR"));
}
@Test
void getEquipmentStatsByOwnership_shouldReturnStatsMap() {
Object[] stat1 = new Object[] {OwnershipType.PROJECT, 10L};
Object[] stat2 = new Object[] {OwnershipType.OWNER, 5L};
when(equipmentRepository.countByOwnership(projectId))
.thenReturn(Arrays.asList(stat1, stat2));
Map<String, Long> result = equipmentService.getEquipmentStatsByOwnership(projectId);
assertEquals(2, result.size());
assertEquals(10L, result.get("PROJECT"));
assertEquals(5L, result.get("OWNER"));
}
@Test
void countByProject_shouldReturnCount() {
when(equipmentRepository.countByProject(projectId)).thenReturn(15L);
long result = equipmentService.countByProject(projectId);
assertEquals(15L, result);
}
@Test
void deleteEquipmentBatch_shouldDeleteMultipleEquipments() {
UUID id1 = UUID.randomUUID();
UUID id2 = UUID.randomUUID();
Equipment eq1 = new Equipment();
eq1.setId(id1);
Equipment eq2 = new Equipment();
eq2.setId(id2);
when(equipmentRepository.findByIdAndIsDeletedFalse(id1)).thenReturn(Optional.of(eq1));
when(equipmentRepository.findByIdAndIsDeletedFalse(id2)).thenReturn(Optional.of(eq2));
when(equipmentRepository.save(any(Equipment.class)))
.thenAnswer(invocation -> invocation.getArgument(0));
equipmentService.deleteEquipmentBatch(Arrays.asList(id1, id2));
assertTrue(eq1.getIsDeleted());
assertTrue(eq2.getIsDeleted());
verify(equipmentRepository, times(2)).save(any(Equipment.class));
}
@Test
void updateEquipment_shouldUpdateAllFields() {
Equipment updateData = new Equipment();
updateData.setEquipmentName("New Name");
updateData.setEquipmentType(EquipmentType.FIRE_PROTECTION);
updateData.setSystemType(SystemType.FIRE_PROTECTION);
updateData.setOwnershipType(OwnershipType.OWNER);
updateData.setOwningEntityId(UUID.randomUUID());
updateData.setOwningEntityName("Owner Entity");
updateData.setAssetCode("ASSET-001");
updateData.setSerialNumber("SN-001");
updateData.setModel("Model-X");
updateData.setManufacturer("Manufacturer-X");
updateData.setSupplier("Supplier-X");
updateData.setStatus(EquipmentStatus.MAINTENANCE);
updateData.setOperationStatus("running");
updateData.setInstallationLocation("1F");
updateData.setInstallationDate(LocalDate.of(2023, 1, 1));
updateData.setDesignLifeYears(20);
updateData.setRatedPower(new BigDecimal("100.50"));
updateData.setRatedVoltage("220V");
updateData.setRatedCurrent(new BigDecimal("50.25"));
updateData.setMaintenanceVendor("Vendor-X");
updateData.setMaintenanceVendorContact("Contact-X");
updateData.setMaintenanceVendorPhone("123456");
updateData.setMaintenanceContractNo("CONTRACT-001");
updateData.setMaintenanceContractStart(LocalDate.of(2023, 1, 1));
updateData.setMaintenanceContractEnd(LocalDate.of(2024, 1, 1));
updateData.setEnergyConsumptionStandard(new BigDecimal("80.00"));
updateData.setInspectionCycle(12);
updateData.setNextInspectionDate(LocalDate.of(2024, 6, 1));
updateData.setLastInspectionDate(LocalDate.of(2023, 6, 1));
updateData.setLastInspectionResult("合格");
updateData.setSpecialEquipmentType("特种设备");
updateData.setSpecialEquipmentCert("CERT-001");
updateData.setRemarks("测试备注");
when(equipmentRepository.findByIdAndIsDeletedFalse(testId))
.thenReturn(Optional.of(testEquipment));
when(equipmentRepository.save(any(Equipment.class)))
.thenAnswer(invocation -> invocation.getArgument(0));
Equipment result = equipmentService.updateEquipment(testId, updateData);
assertEquals("New Name", result.getEquipmentName());
assertEquals(EquipmentType.FIRE_PROTECTION, result.getEquipmentType());
assertEquals(SystemType.FIRE_PROTECTION, result.getSystemType());
assertEquals(OwnershipType.OWNER, result.getOwnershipType());
assertEquals("ASSET-001", result.getAssetCode());
assertEquals("SN-001", result.getSerialNumber());
assertEquals("Model-X", result.getModel());
assertEquals("Manufacturer-X", result.getManufacturer());
assertEquals("Supplier-X", result.getSupplier());
assertEquals(EquipmentStatus.MAINTENANCE, result.getStatus());
assertEquals("running", result.getOperationStatus());
assertEquals("1F", result.getInstallationLocation());
assertEquals(LocalDate.of(2023, 1, 1), result.getInstallationDate());
assertEquals(20, result.getDesignLifeYears());
assertEquals(new BigDecimal("100.50"), result.getRatedPower());
assertEquals("220V", result.getRatedVoltage());
assertEquals(new BigDecimal("50.25"), result.getRatedCurrent());
assertEquals("Vendor-X", result.getMaintenanceVendor());
assertEquals("Contact-X", result.getMaintenanceVendorContact());
assertEquals("123456", result.getMaintenanceVendorPhone());
assertEquals("CONTRACT-001", result.getMaintenanceContractNo());
assertEquals(LocalDate.of(2023, 1, 1), result.getMaintenanceContractStart());
assertEquals(LocalDate.of(2024, 1, 1), result.getMaintenanceContractEnd());
assertEquals(new BigDecimal("80.00"), result.getEnergyConsumptionStandard());
assertEquals(12, result.getInspectionCycle());
assertEquals(LocalDate.of(2024, 6, 1), result.getNextInspectionDate());
assertEquals(LocalDate.of(2023, 6, 1), result.getLastInspectionDate());
assertEquals("合格", result.getLastInspectionResult());
assertEquals("特种设备", result.getSpecialEquipmentType());
assertEquals("CERT-001", result.getSpecialEquipmentCert());
assertEquals("测试备注", result.getRemarks());
}
}

View File

@ -1,60 +1,38 @@
package com.ether.pms.auth.annotation; package com.ether.pms.auth.annotation;
import com.ether.pms.auth.entity.AuditLog; import com.ether.pms.auth.entity.AuditLog;
import java.lang.annotation.*; import java.lang.annotation.*;
/** /** 审计日志注解 用于标记需要记录审计日志的方法 */
* 审计日志注解
* 用于标记需要记录审计日志的方法
*/
@Target(ElementType.METHOD) @Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Documented @Documented
public @interface OperationLog { public @interface OperationLog {
/** /** 操作描述 */
* 操作描述
*/
String operation(); String operation();
/** /** 功能模块 */
* 功能模块
*/
String module(); String module();
/** /** 操作类型 */
* 操作类型
*/
AuditLog.ActionType action() default AuditLog.ActionType.UPDATE; AuditLog.ActionType action() default AuditLog.ActionType.UPDATE;
/** /** 目标对象类型支持SpEL表达式 */
* 目标对象类型支持SpEL表达式
*/
String targetType() default ""; String targetType() default "";
/** /** 目标对象ID支持SpEL表达式 */
* 目标对象ID支持SpEL表达式
*/
String targetId() default ""; String targetId() default "";
/** /** 操作内容支持SpEL表达式 */
* 操作内容支持SpEL表达式
*/
String content() default ""; String content() default "";
/** /** 是否记录请求参数 */
* 是否记录请求参数
*/
boolean recordParams() default true; boolean recordParams() default true;
/** /** 是否记录响应结果 */
* 是否记录响应结果
*/
boolean recordResult() default false; boolean recordResult() default false;
/** /** 需要过滤的敏感字段(不记录) */
* 需要过滤的敏感字段不记录
*/
String[] sensitiveFields() default {"password", "token", "secret", "creditCard"}; String[] sensitiveFields() default {"password", "token", "secret", "creditCard"};
} }

View File

@ -1,13 +1,17 @@
package com.ether.pms.auth.aspect; package com.ether.pms.auth.aspect;
import com.ether.pms.auth.annotation.OperationLog; import com.ether.pms.auth.annotation.OperationLog;
import com.ether.pms.auth.entity.AuditLog.ActionType;
import com.ether.pms.auth.entity.AuditLog.Status; import com.ether.pms.auth.entity.AuditLog.Status;
import com.ether.pms.auth.service.AuditLogService; import com.ether.pms.auth.service.AuditLogService;
import com.ether.pms.auth.util.SecurityUtils; import com.ether.pms.auth.util.SecurityUtils;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.ProceedingJoinPoint;
@ -18,12 +22,6 @@ import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.context.request.ServletRequestAttributes;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
@Aspect @Aspect
@Component @Component
@RequiredArgsConstructor @RequiredArgsConstructor
@ -111,7 +109,8 @@ public class AuditLogAspect {
} }
private HttpServletRequest getRequest() { private HttpServletRequest getRequest() {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); ServletRequestAttributes attributes =
(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes != null) { if (attributes != null) {
return attributes.getRequest(); return attributes.getRequest();
} }

View File

@ -1,19 +1,16 @@
package com.ether.pms.auth.config; package com.ether.pms.auth.config;
import java.util.concurrent.Executor;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
@Configuration @Configuration
@EnableAsync @EnableAsync
public class AsyncConfig { public class AsyncConfig {
/** /** 审计日志异步执行器 */
* 审计日志异步执行器
*/
@Bean("auditLogExecutor") @Bean("auditLogExecutor")
public Executor auditLogExecutor() { public Executor auditLogExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
@ -21,7 +18,8 @@ public class AsyncConfig {
executor.setMaxPoolSize(5); executor.setMaxPoolSize(5);
executor.setQueueCapacity(100); executor.setQueueCapacity(100);
executor.setThreadNamePrefix("audit-log-"); executor.setThreadNamePrefix("audit-log-");
executor.setRejectedExecutionHandler(new java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy()); executor.setRejectedExecutionHandler(
new java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize(); executor.initialize();
return executor; return executor;
} }

View File

@ -6,6 +6,8 @@ import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException; import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
@ -15,25 +17,21 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.intercept.AuthorizationFilter; import org.springframework.security.web.access.intercept.AuthorizationFilter;
import org.springframework.security.web.context.SecurityContextRepository;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository; import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.web.context.SecurityContextRepository;
import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
import java.util.List;
@Configuration @Configuration
@EnableWebSecurity @EnableWebSecurity
@RequiredArgsConstructor @RequiredArgsConstructor
@ -53,25 +51,33 @@ public class SecurityConfig {
@Bean @Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http http.csrf(AbstractHttpConfigurer::disable)
.csrf(AbstractHttpConfigurer::disable)
.cors(cors -> cors.configurationSource(corsConfigurationSource())) .cors(cors -> cors.configurationSource(corsConfigurationSource()))
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)) .sessionManagement(
.securityContext(context -> context session -> session.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED))
.securityContextRepository(securityContextRepository()) .securityContext(
) context -> context.securityContextRepository(securityContextRepository()))
.authorizeHttpRequests(auth -> auth .authorizeHttpRequests(
.requestMatchers("/api/auth/login", "/api/auth/logout", "/api/auth/refresh").permitAll() auth ->
auth.requestMatchers(
"/api/auth/login",
"/api/auth/logout",
"/api/auth/refresh")
.permitAll()
// 根据配置动态控制 Swagger/API 文档访问 // 根据配置动态控制 Swagger/API 文档访问
// 开发环境允许所有用户访问便于开发调试 // 开发环境允许所有用户访问便于开发调试
// 生产环境即使 URL 匹配springdoc 也会返回 404因为已禁用 // 生产环境即使 URL 匹配springdoc 也会返回 404因为已禁用
.requestMatchers("/api-docs/**", "/swagger-ui/**", "/swagger-ui.html", "/v3/api-docs/**").permitAll() .requestMatchers(
.requestMatchers("/actuator/**").hasRole("ADMIN") // 需要管理员权限 "/api-docs/**",
.anyRequest().authenticated() "/swagger-ui/**",
) "/swagger-ui.html",
.exceptionHandling(ex -> ex "/v3/api-docs/**")
.authenticationEntryPoint(unauthorizedEntryPoint()) .permitAll()
) .requestMatchers("/actuator/**")
.hasRole("ADMIN") // 需要管理员权限
.anyRequest()
.authenticated())
.exceptionHandling(ex -> ex.authenticationEntryPoint(unauthorizedEntryPoint()))
.addFilterBefore(jwtAuthenticationFilter(), AuthorizationFilter.class); .addFilterBefore(jwtAuthenticationFilter(), AuthorizationFilter.class);
return http.build(); return http.build();
@ -80,7 +86,8 @@ public class SecurityConfig {
@Bean @Bean
public CorsConfigurationSource corsConfigurationSource() { public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration(); CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(List.of( configuration.setAllowedOrigins(
List.of(
"http://127.0.0.1:5173", "http://127.0.0.1:5173",
"http://127.0.0.1:5174", "http://127.0.0.1:5174",
"http://127.0.0.1:5175", "http://127.0.0.1:5175",
@ -96,8 +103,7 @@ public class SecurityConfig {
"http://localhost:5177", "http://localhost:5177",
"http://localhost:5178", "http://localhost:5178",
"http://localhost:5179", "http://localhost:5179",
"http://localhost:5180" "http://localhost:5180"));
));
configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS")); configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
configuration.setAllowedHeaders(List.of("*")); configuration.setAllowedHeaders(List.of("*"));
configuration.setAllowCredentials(true); configuration.setAllowCredentials(true);
@ -125,14 +131,18 @@ public class SecurityConfig {
public OncePerRequestFilter jwtAuthenticationFilter() { public OncePerRequestFilter jwtAuthenticationFilter() {
return new OncePerRequestFilter() { return new OncePerRequestFilter() {
@Override @Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException { throws ServletException, IOException {
String token = resolveToken(request); String token = resolveToken(request);
if (token != null && jwtTokenProvider.validateToken(token)) { if (token != null && jwtTokenProvider.validateToken(token)) {
String username = jwtTokenProvider.getUsernameFromToken(token); String username = jwtTokenProvider.getUsernameFromToken(token);
List<GrantedAuthority> authorities = jwtTokenProvider.getAuthoritiesFromToken(token); List<GrantedAuthority> authorities =
jwtTokenProvider.getAuthoritiesFromToken(token);
UsernamePasswordAuthenticationToken authentication = UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(username, token, authorities); new UsernamePasswordAuthenticationToken(username, token, authorities);

View File

@ -4,6 +4,11 @@ import com.ether.pms.auth.entity.AuditLog;
import com.ether.pms.auth.service.AuditLogService; import com.ether.pms.auth.service.AuditLogService;
import com.ether.pms.common.ApiResponse; import com.ether.pms.common.ApiResponse;
import com.ether.pms.common.util.PaginationValidator; import com.ether.pms.common.util.PaginationValidator;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
@ -14,12 +19,6 @@ import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@RestController @RestController
@RequestMapping("/api/audit-logs") @RequestMapping("/api/audit-logs")
@RequiredArgsConstructor @RequiredArgsConstructor
@ -29,9 +28,7 @@ public class AuditLogController {
private final AuditLogService auditLogService; private final AuditLogService auditLogService;
/** /** 查询审计日志列表最近30天 */
* 查询审计日志列表最近30天
*/
@GetMapping @GetMapping
public ApiResponse<Page<AuditLog>> list( public ApiResponse<Page<AuditLog>> list(
@RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "0") int page,
@ -39,57 +36,53 @@ public class AuditLogController {
@RequestParam(required = false) String module, @RequestParam(required = false) String module,
@RequestParam(required = false) String action, @RequestParam(required = false) String action,
@RequestParam(required = false) String username, @RequestParam(required = false) String username,
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime startDate, @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime endDate) { LocalDateTime startDate,
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
LocalDateTime endDate) {
// 使用 PaginationValidator 校验分页参数防止 OOM 和数据库过载 // 使用 PaginationValidator 校验分页参数防止 OOM 和数据库过载
int safeSize = PaginationValidator.getSafeSize(size); int safeSize = PaginationValidator.getSafeSize(size);
Pageable pageable = PageRequest.of(page, safeSize, Sort.by("createdAt").descending()); Pageable pageable = PageRequest.of(page, safeSize, Sort.by("createdAt").descending());
Page<AuditLog> result = auditLogService.searchLogs(module, action, username, startDate, endDate, pageable); Page<AuditLog> result =
auditLogService.searchLogs(module, action, username, startDate, endDate, pageable);
return ApiResponse.success(result); return ApiResponse.success(result);
} }
/** /** 获取模块列表(用于筛选) */
* 获取模块列表用于筛选
*/
@GetMapping("/modules") @GetMapping("/modules")
public ApiResponse<List<Map<String, String>>> getModules() { public ApiResponse<List<Map<String, String>>> getModules() {
List<Map<String, String>> modules = Arrays.asList( List<Map<String, String>> modules =
Arrays.asList(
Map.of("value", "USER", "label", "用户管理"), Map.of("value", "USER", "label", "用户管理"),
Map.of("value", "ROLE", "label", "角色管理"), Map.of("value", "ROLE", "label", "角色管理"),
Map.of("value", "PERMISSION", "label", "权限管理"), Map.of("value", "PERMISSION", "label", "权限管理"),
Map.of("value", "PROJECT", "label", "项目管理"), Map.of("value", "PROJECT", "label", "项目管理"),
Map.of("value", "AUTH", "label", "登录认证") Map.of("value", "AUTH", "label", "登录认证"));
);
return ApiResponse.success(modules); return ApiResponse.success(modules);
} }
/** /** 获取操作类型列表(用于筛选) */
* 获取操作类型列表用于筛选
*/
@GetMapping("/actions") @GetMapping("/actions")
public ApiResponse<List<Map<String, String>>> getActions() { public ApiResponse<List<Map<String, String>>> getActions() {
List<Map<String, String>> actions = Arrays.stream(AuditLog.ActionType.values()) List<Map<String, String>> actions =
.map(action -> Map.of( Arrays.stream(AuditLog.ActionType.values())
.map(
action ->
Map.of(
"value", action.name(), "value", action.name(),
"label", action.getDesc() "label", action.getDesc()))
))
.collect(Collectors.toList()); .collect(Collectors.toList());
return ApiResponse.success(actions); return ApiResponse.success(actions);
} }
/** /** 获取最近30天的日志统计 */
* 获取最近30天的日志统计
*/
@GetMapping("/stats") @GetMapping("/stats")
public ApiResponse<Map<String, Object>> getStats() { public ApiResponse<Map<String, Object>> getStats() {
long count = auditLogService.getRecentLogCount(); long count = auditLogService.getRecentLogCount();
return ApiResponse.success(Map.of( return ApiResponse.success(
"total", count, Map.of("total", count, "retentionDays", 30, "description", "最近30天的审计日志数量"));
"retentionDays", 30,
"description", "最近30天的审计日志数量"
));
} }
} }

View File

@ -7,15 +7,14 @@ import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size; import jakarta.validation.constraints.Size;
import java.util.HashMap;
import java.util.Map;
import lombok.Data; import lombok.Data;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@RestController @RestController
@RequestMapping("/api/auth") @RequestMapping("/api/auth")
@RequiredArgsConstructor @RequiredArgsConstructor
@ -27,16 +26,17 @@ public class AuthController {
@PostMapping("/login") @PostMapping("/login")
public ResponseEntity<ApiResponse<Map<String, Object>>> login( public ResponseEntity<ApiResponse<Map<String, Object>>> login(
@Valid @RequestBody LoginRequest request, @Valid @RequestBody LoginRequest request, HttpServletRequest httpRequest) {
HttpServletRequest httpRequest) {
String ip = getClientIp(httpRequest); String ip = getClientIp(httpRequest);
Map<String, Object> result = loginService.login(request.getUsername(), request.getPassword(), ip); Map<String, Object> result =
loginService.login(request.getUsername(), request.getPassword(), ip);
return ResponseEntity.ok(ApiResponse.success(result)); return ResponseEntity.ok(ApiResponse.success(result));
} }
@PostMapping("/logout") @PostMapping("/logout")
public ResponseEntity<ApiResponse<Void>> logout(@RequestHeader(value = "Authorization", required = false) String token) { public ResponseEntity<ApiResponse<Void>> logout(
@RequestHeader(value = "Authorization", required = false) String token) {
return ResponseEntity.ok(ApiResponse.success()); return ResponseEntity.ok(ApiResponse.success());
} }
@ -78,8 +78,8 @@ public class AuthController {
} }
String username = jwtTokenProvider.getUsernameFromToken(jwt); String username = jwtTokenProvider.getUsernameFromToken(jwt);
String newToken = jwtTokenProvider.generateToken( String newToken =
jwtTokenProvider.getUserIdFromToken(jwt), username); jwtTokenProvider.generateToken(jwtTokenProvider.getUserIdFromToken(jwt), username);
Map<String, Object> result = new HashMap<>(); Map<String, Object> result = new HashMap<>();
result.put("token", newToken); result.put("token", newToken);

View File

@ -1,56 +0,0 @@
package com.ether.pms.auth.controller;
import com.ether.pms.auth.controller.dto.DataAccessRequest;
import com.ether.pms.auth.entity.DataAccess;
import com.ether.pms.auth.service.DataAccessService;
import com.ether.pms.auth.util.SecurityUtils;
import com.ether.pms.common.ApiResponse;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.UUID;
@RestController
@RequestMapping("/api/data-access")
@RequiredArgsConstructor
@Validated
public class DataAccessController {
private final DataAccessService dataAccessService;
@Deprecated
@PostMapping
public ResponseEntity<ApiResponse<Void>> grantAccess(@Valid @RequestBody DataAccessRequest request) {
UUID currentUserId = SecurityUtils.getCurrentUserId();
if (currentUserId == null) {
return ResponseEntity.status(401).body(ApiResponse.error(401, "未登录"));
}
dataAccessService.grantAccess(
request.getDataType(),
request.getDataId(),
request.getAccessType(),
request.getAccessId(),
request.getAccessLevel(),
currentUserId
);
return ResponseEntity.ok(ApiResponse.success());
}
@Deprecated
@DeleteMapping("/{id}")
public ResponseEntity<ApiResponse<Void>> revokeAccess(@PathVariable UUID id) {
dataAccessService.revokeAccess(id);
return ResponseEntity.ok(ApiResponse.success());
}
@Deprecated
@GetMapping
public ResponseEntity<ApiResponse<List<DataAccess>>> getDataAccess(
@RequestParam String dataType,
@RequestParam UUID dataId) {
return ResponseEntity.ok(ApiResponse.success(dataAccessService.getDataAccess(dataType, dataId)));
}
}

View File

@ -10,18 +10,17 @@ import com.ether.pms.auth.entity.User;
import com.ether.pms.auth.service.DeptService; import com.ether.pms.auth.service.DeptService;
import com.ether.pms.common.ApiResponse; import com.ether.pms.common.ApiResponse;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
/** /**
* 部门管理控制器 * 部门管理控制器
* *
* <p>提供部门相关的RESTful API接口包括部门树查询创建部门获取部门成员等功能</p> * <p>提供部门相关的RESTful API接口包括部门树查询创建部门获取部门成员等功能
* *
* @author Ether开发团队 * @author Ether开发团队
* @version 1.0.0 * @version 1.0.0
@ -35,9 +34,7 @@ public class DeptController {
private final DeptService deptService; private final DeptService deptService;
/** /** 获取部门树 */
* 获取部门树
*/
@GetMapping("/tree") @GetMapping("/tree")
public ApiResponse<List<DeptVO>> getDeptTree() { public ApiResponse<List<DeptVO>> getDeptTree() {
List<Dept> depts = deptService.getDeptTree(); List<Dept> depts = deptService.getDeptTree();
@ -45,27 +42,19 @@ public class DeptController {
return ApiResponse.success(tree); return ApiResponse.success(tree);
} }
/** /** 获取所有启用的部门列表 */
* 获取所有启用的部门列表
*/
@GetMapping @GetMapping
public ApiResponse<List<Dept>> getAllDepts() { public ApiResponse<List<Dept>> getAllDepts() {
return ApiResponse.success(deptService.getActiveDepts()); return ApiResponse.success(deptService.getActiveDepts());
} }
/** /** 根据ID获取部门 */
* 根据ID获取部门
*/
@GetMapping("/{id}") @GetMapping("/{id}")
public ApiResponse<Dept> getById(@PathVariable UUID id) { public ApiResponse<Dept> getById(@PathVariable UUID id) {
return deptService.getById(id) return deptService.getById(id).map(ApiResponse::success).orElse(ApiResponse.error("部门不存在"));
.map(ApiResponse::success)
.orElse(ApiResponse.error("部门不存在"));
} }
/** /** 创建部门 */
* 创建部门
*/
@PostMapping @PostMapping
@OperationLog(operation = "创建部门", module = "DEPT", action = AuditLog.ActionType.CREATE) @OperationLog(operation = "创建部门", module = "DEPT", action = AuditLog.ActionType.CREATE)
public ApiResponse<Dept> createDept(@RequestBody @Valid DeptDTO dto) { public ApiResponse<Dept> createDept(@RequestBody @Valid DeptDTO dto) {
@ -79,9 +68,7 @@ public class DeptController {
return ApiResponse.success(deptService.createDept(dept)); return ApiResponse.success(deptService.createDept(dept));
} }
/** /** 更新部门 */
* 更新部门
*/
@PutMapping("/{id}") @PutMapping("/{id}")
@OperationLog(operation = "更新部门", module = "DEPT", action = AuditLog.ActionType.UPDATE) @OperationLog(operation = "更新部门", module = "DEPT", action = AuditLog.ActionType.UPDATE)
public ApiResponse<Dept> updateDept(@PathVariable UUID id, @RequestBody @Valid DeptDTO dto) { public ApiResponse<Dept> updateDept(@PathVariable UUID id, @RequestBody @Valid DeptDTO dto) {
@ -95,9 +82,7 @@ public class DeptController {
return ApiResponse.success(deptService.updateDept(id, dept)); return ApiResponse.success(deptService.updateDept(id, dept));
} }
/** /** 删除部门 */
* 删除部门
*/
@DeleteMapping("/{id}") @DeleteMapping("/{id}")
@OperationLog(operation = "删除部门", module = "DEPT", action = AuditLog.ActionType.DELETE) @OperationLog(operation = "删除部门", module = "DEPT", action = AuditLog.ActionType.DELETE)
public ApiResponse<Void> deleteDept(@PathVariable UUID id) { public ApiResponse<Void> deleteDept(@PathVariable UUID id) {
@ -105,18 +90,15 @@ public class DeptController {
return ApiResponse.success(); return ApiResponse.success();
} }
/** /** 获取部门成员 */
* 获取部门成员
*/
@GetMapping("/{deptId}/members") @GetMapping("/{deptId}/members")
public ApiResponse<List<UserVO>> getDeptMembers(@PathVariable UUID deptId) { public ApiResponse<List<UserVO>> getDeptMembers(@PathVariable UUID deptId) {
List<User> members = deptService.getDeptEmployees(deptId); List<User> members = deptService.getDeptEmployees(deptId);
return ApiResponse.success(members.stream().map(UserVO::fromEntity).collect(Collectors.toList())); return ApiResponse.success(
members.stream().map(UserVO::fromEntity).collect(Collectors.toList()));
} }
/** /** 根据部门类型查询部门 */
* 根据部门类型查询部门
*/
@GetMapping("/by-type/{deptType}") @GetMapping("/by-type/{deptType}")
public ApiResponse<List<Dept>> getByType(@PathVariable String deptType) { public ApiResponse<List<Dept>> getByType(@PathVariable String deptType) {
return ApiResponse.success(deptService.getByType(deptType)); return ApiResponse.success(deptService.getByType(deptType));
@ -124,9 +106,12 @@ public class DeptController {
private List<DeptVO> buildDeptTree(List<Dept> depts, UUID parentId) { private List<DeptVO> buildDeptTree(List<Dept> depts, UUID parentId) {
return depts.stream() return depts.stream()
.filter(d -> (parentId == null && d.getParentId() == null) || .filter(
(parentId != null && parentId.equals(d.getParentId()))) d ->
.map(d -> { (parentId == null && d.getParentId() == null)
|| (parentId != null && parentId.equals(d.getParentId())))
.map(
d -> {
DeptVO vo = DeptVO.fromEntity(d); DeptVO vo = DeptVO.fromEntity(d);
vo.setChildren(buildDeptTree(depts, d.getId())); vo.setChildren(buildDeptTree(depts, d.getId()));
return vo; return vo;

View File

@ -2,35 +2,36 @@ package com.ether.pms.auth.controller;
import com.ether.pms.auth.entity.Permission; import com.ether.pms.auth.entity.Permission;
import com.ether.pms.auth.service.PermissionService; import com.ether.pms.auth.service.PermissionService;
import com.ether.pms.auth.util.SecurityUtils;
import com.ether.pms.auth.vo.MenuVO;
import com.ether.pms.auth.vo.PermissionVO;
import com.ether.pms.common.ApiResponse; import com.ether.pms.common.ApiResponse;
import com.ether.pms.common.util.PaginationValidator;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import java.util.List;
import java.util.UUID;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.UUID;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import com.ether.pms.common.util.PaginationValidator;
/** /**
* 权限管理REST接口控制器 * 权限管理REST接口控制器
* *
* <p>提供权限Permission相关的HTTP API接口包括权限的增删改查类型筛选菜单权限获取等功能 * <p>提供权限Permission相关的HTTP API接口包括权限的增删改查类型筛选菜单权限获取等功能 所有接口均遵循RESTful设计规范返回统一的ApiResponse格式
* 所有接口均遵循RESTful设计规范返回统一的ApiResponse格式</p> *
* <p>主要接口
* *
* <p>主要接口</p>
* <ul> * <ul>
* <li>GET /api/auth/permissions - 查询所有权限</li> * <li>GET /api/auth/permissions - 查询所有权限
* <li>GET /api/auth/permissions/{id} - 根据ID查询权限</li> * <li>GET /api/auth/permissions/{id} - 根据ID查询权限
* <li>GET /api/auth/permissions/type/{type} - 根据类型查询权限</li> * <li>GET /api/auth/permissions/type/{type} - 根据类型查询权限
* <li>GET /api/auth/permissions/menus - 查询所有菜单权限</li> * <li>GET /api/auth/permissions/menus - 查询所有菜单权限
* <li>POST /api/auth/permissions - 创建权限</li> * <li>POST /api/auth/permissions - 创建权限
* <li>PUT /api/auth/permissions/{id} - 更新权限</li> * <li>PUT /api/auth/permissions/{id} - 更新权限
* <li>DELETE /api/auth/permissions/{id} - 删除权限</li> * <li>DELETE /api/auth/permissions/{id} - 删除权限
* </ul> * </ul>
* *
* @author Ether开发团队 * @author Ether开发团队
@ -75,7 +76,7 @@ public class PermissionController {
/** /**
* 根据权限类型查询权限列表 * 根据权限类型查询权限列表
* *
* <p>按权限类型筛选如MENUBUTTONAPI等</p> * <p>按权限类型筛选如MENUBUTTONAPI等
* *
* @param type 权限类型 * @param type 权限类型
* @return 包含该类型权限列表的响应 * @return 包含该类型权限列表的响应
@ -88,46 +89,48 @@ public class PermissionController {
/** /**
* 查询所有菜单权限 * 查询所有菜单权限
* *
* <p>获取type为MENU的所有权限通常用于前端菜单渲染</p> * <p>获取type为MENU的所有权限通常用于管理员查看所有菜单配置
* *
* @return 包含菜单权限列表的响应 * @return 包含菜单权限列表的响应
*/ */
@GetMapping("/menus") @GetMapping("/all-menus")
public ResponseEntity<ApiResponse<List<Permission>>> findMenus() { public ResponseEntity<ApiResponse<List<Permission>>> findAllMenus() {
return ResponseEntity.ok(ApiResponse.success(permissionService.findMenuPermissions())); return ResponseEntity.ok(ApiResponse.success(permissionService.findMenuPermissions()));
} }
/** /**
* 创建新权限 * 创建新权限
* *
* <p>创建一个新的权限</p> * <p>创建一个新的权限
* *
* @param permission 要创建的权限信息请求体 * @param permission 要创建的权限信息请求体
* @return 包含创建后权限信息的响应 * @return 包含创建后权限信息的响应
*/ */
@PostMapping @PostMapping
public ResponseEntity<ApiResponse<Permission>> create(@Valid @RequestBody Permission permission) { public ResponseEntity<ApiResponse<Permission>> create(
@Valid @RequestBody Permission permission) {
return ResponseEntity.ok(ApiResponse.success(permissionService.create(permission))); return ResponseEntity.ok(ApiResponse.success(permissionService.create(permission)));
} }
/** /**
* 更新权限信息 * 更新权限信息
* *
* <p>更新指定权限的信息</p> * <p>更新指定权限的信息
* *
* @param id 要更新的权限ID * @param id 要更新的权限ID
* @param permission 包含新数据的权限信息请求体 * @param permission 包含新数据的权限信息请求体
* @return 包含更新后权限信息的响应 * @return 包含更新后权限信息的响应
*/ */
@PutMapping("/{id}") @PutMapping("/{id}")
public ResponseEntity<ApiResponse<Permission>> update(@PathVariable UUID id, @Valid @RequestBody Permission permission) { public ResponseEntity<ApiResponse<Permission>> update(
@PathVariable UUID id, @Valid @RequestBody Permission permission) {
return ResponseEntity.ok(ApiResponse.success(permissionService.update(id, permission))); return ResponseEntity.ok(ApiResponse.success(permissionService.update(id, permission)));
} }
/** /**
* 删除权限 * 删除权限
* *
* <p>删除指定的权限</p> * <p>删除指定的权限
* *
* @param id 要删除的权限ID * @param id 要删除的权限ID
* @return 空响应表示操作成功 * @return 空响应表示操作成功
@ -137,4 +140,36 @@ public class PermissionController {
permissionService.delete(id); permissionService.delete(id);
return ResponseEntity.ok(ApiResponse.success()); return ResponseEntity.ok(ApiResponse.success());
} }
/**
* 获取当前用户菜单
*
* <p>返回当前用户有权限访问的菜单用于前端动态渲染菜单
*
* @param projectId 项目ID可选
* @return 用户菜单列表
*/
@GetMapping("/menus")
public ResponseEntity<ApiResponse<List<MenuVO>>> getUserMenus(
@RequestParam(required = false) UUID projectId) {
UUID userId = SecurityUtils.getCurrentUserId();
return ResponseEntity.ok(
ApiResponse.success(permissionService.getUserMenus(userId, projectId)));
}
/**
* 获取当前用户权限
*
* <p>返回当前用户的所有权限标识用于按钮级别权限控制
*
* @param projectId 项目ID可选
* @return 用户权限标识列表
*/
@GetMapping("/permissions")
public ResponseEntity<ApiResponse<PermissionVO>> getUserPermissions(
@RequestParam(required = false) UUID projectId) {
UUID userId = SecurityUtils.getCurrentUserId();
return ResponseEntity.ok(
ApiResponse.success(permissionService.getUserPermissions(userId, projectId)));
}
} }

View File

@ -0,0 +1,24 @@
package com.ether.pms.auth.controller;
import com.ether.pms.auth.service.PermissionService;
import com.ether.pms.auth.vo.PermissionTreeVO;
import com.ether.pms.common.ApiResponse;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/permissions")
@RequiredArgsConstructor
public class PermissionTreeController {
private final PermissionService permissionService;
@GetMapping("/tree")
public ResponseEntity<ApiResponse<List<PermissionTreeVO>>> getPermissionTree() {
return ResponseEntity.ok(ApiResponse.success(permissionService.getPermissionTree()));
}
}

View File

@ -1,19 +1,5 @@
package com.ether.pms.auth.controller; package com.ether.pms.auth.controller;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.ether.pms.auth.annotation.OperationLog; import com.ether.pms.auth.annotation.OperationLog;
import com.ether.pms.auth.controller.dto.AddProjectMemberDTO; import com.ether.pms.auth.controller.dto.AddProjectMemberDTO;
import com.ether.pms.auth.controller.dto.PageResponse; import com.ether.pms.auth.controller.dto.PageResponse;
@ -25,16 +11,27 @@ import com.ether.pms.auth.entity.User;
import com.ether.pms.auth.service.UserManagementService; import com.ether.pms.auth.service.UserManagementService;
import com.ether.pms.common.ApiResponse; import com.ether.pms.common.ApiResponse;
import com.ether.pms.common.util.PaginationValidator; import com.ether.pms.common.util.PaginationValidator;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/** /**
* 项目成员管理控制器 * 项目成员管理控制器
* *
* <p>提供项目成员相关的RESTful API接口用于管理项目成员列表添加成员移除成员等功能</p> * <p>提供项目成员相关的RESTful API接口用于管理项目成员列表添加成员移除成员等功能
* *
* <p>所有接口路径前缀为/api/auth/projects/{projectId}/members</p> * <p>所有接口路径前缀为/api/auth/projects/{projectId}/members
* *
* @author Ether开发团队 * @author Ether开发团队
* @version 1.0.0 * @version 1.0.0
@ -52,7 +49,7 @@ public class ProjectMemberController {
/** /**
* 查询项目成员列表 * 查询项目成员列表
* *
* <p>分页返回指定项目下的所有成员信息包含角色信息</p> * <p>分页返回指定项目下的所有成员信息包含角色信息
* *
* @param projectId 项目唯一标识符 * @param projectId 项目唯一标识符
* @param page 页码从1开始 * @param page 页码从1开始
@ -74,17 +71,19 @@ public class ProjectMemberController {
if (start < 0) start = 0; if (start < 0) start = 0;
if (start > staffList.size()) start = staffList.size(); if (start > staffList.size()) start = staffList.size();
int end = Math.min(start + safeSize, staffList.size()); int end = Math.min(start + safeSize, staffList.size());
List<ProjectMemberVO> pageData = staffList.subList(start, end).stream() List<ProjectMemberVO> pageData =
staffList.subList(start, end).stream()
.map(ProjectMemberVO::fromEntity) .map(ProjectMemberVO::fromEntity)
.collect(Collectors.toList()); .collect(Collectors.toList());
PageResponse<ProjectMemberVO> response = new PageResponse<>(pageData, (long) staffList.size(), pageIndex + 1, size); PageResponse<ProjectMemberVO> response =
new PageResponse<>(pageData, (long) staffList.size(), pageIndex + 1, size);
return ApiResponse.success(response); return ApiResponse.success(response);
} }
/** /**
* 获取可添加到项目的成员列表企业员工 * 获取可添加到项目的成员列表企业员工
* *
* <p>返回尚未加入该项目的所有企业员工列表支持模糊搜索用户</p> * <p>返回尚未加入该项目的所有企业员工列表支持模糊搜索用户
* *
* @param projectId 项目唯一标识符 * @param projectId 项目唯一标识符
* @param search 搜索关键字可选支持用户名和真实姓名模糊匹配 * @param search 搜索关键字可选支持用户名和真实姓名模糊匹配
@ -92,60 +91,65 @@ public class ProjectMemberController {
*/ */
@GetMapping("/{projectId}/available-members") @GetMapping("/{projectId}/available-members")
public ApiResponse<List<UserVO>> getAvailableMembers( public ApiResponse<List<UserVO>> getAvailableMembers(
@PathVariable UUID projectId, @PathVariable UUID projectId, @RequestParam(required = false) String search) {
@RequestParam(required = false) String search) {
List<User> users = userManagementService.findEnterpriseUsers(); List<User> users = userManagementService.findEnterpriseUsers();
// 支持模糊搜索 // 支持模糊搜索
if (search != null && !search.isBlank()) { if (search != null && !search.isBlank()) {
String keyword = search.toLowerCase(); String keyword = search.toLowerCase();
users = users.stream() users =
.filter(u -> u.getUsername().toLowerCase().contains(keyword) users.stream()
|| (u.getRealName() != null && u.getRealName().toLowerCase().contains(keyword))) .filter(
u ->
u.getUsername().toLowerCase().contains(keyword)
|| (u.getRealName() != null
&& u.getRealName()
.toLowerCase()
.contains(keyword)))
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
return ApiResponse.success(users.stream() return ApiResponse.success(
.map(UserVO::fromEntity) users.stream().map(UserVO::fromEntity).collect(Collectors.toList()));
.collect(Collectors.toList()));
} }
/** /**
* 添加项目成员 * 添加项目成员
* *
* <p>将指定用户添加到项目中并分配多个角色</p> * <p>将指定用户添加到项目中并分配多个角色
* *
* @param projectId 项目唯一标识符 * @param projectId 项目唯一标识符
* @param dto 添加项目成员的请求数据 * @param dto 添加项目成员的请求数据
* @return 成功响应 * @return 成功响应
*/ */
@PostMapping("/{projectId}/members") @PostMapping("/{projectId}/members")
@OperationLog(operation = "添加项目成员", module = "PROJECT_MEMBER", action = AuditLog.ActionType.CREATE) @OperationLog(
operation = "添加项目成员",
module = "PROJECT_MEMBER",
action = AuditLog.ActionType.CREATE)
public ApiResponse<Void> addProjectMember( public ApiResponse<Void> addProjectMember(
@PathVariable UUID projectId, @PathVariable UUID projectId, @Valid @RequestBody AddProjectMemberDTO dto) {
@Valid @RequestBody AddProjectMemberDTO dto) {
userManagementService.assignStaffToProject( userManagementService.assignStaffToProject(
dto.getUserId(), dto.getUserId(), projectId, dto.getStaffType(), dto.getRoleIds());
projectId,
dto.getStaffType(),
dto.getRoleIds());
return ApiResponse.success(); return ApiResponse.success();
} }
/** /**
* 移除项目成员 * 移除项目成员
* *
* <p>将指定用户从项目中移除</p> * <p>将指定用户从项目中移除
* *
* @param projectId 项目唯一标识符 * @param projectId 项目唯一标识符
* @param userId 用户唯一标识符 * @param userId 用户唯一标识符
* @return 成功响应 * @return 成功响应
*/ */
@DeleteMapping("/{projectId}/members/{userId}") @DeleteMapping("/{projectId}/members/{userId}")
@OperationLog(operation = "移除项目成员", module = "PROJECT_MEMBER", action = AuditLog.ActionType.DELETE) @OperationLog(
operation = "移除项目成员",
module = "PROJECT_MEMBER",
action = AuditLog.ActionType.DELETE)
public ApiResponse<Void> removeProjectMember( public ApiResponse<Void> removeProjectMember(
@PathVariable UUID projectId, @PathVariable UUID projectId, @PathVariable UUID userId) {
@PathVariable UUID userId) {
userManagementService.removeStaffFromProject(userId, projectId); userManagementService.removeStaffFromProject(userId, projectId);
return ApiResponse.success(); return ApiResponse.success();
} }

View File

@ -7,37 +7,34 @@ import com.ether.pms.auth.entity.Role;
import com.ether.pms.auth.entity.User; import com.ether.pms.auth.entity.User;
import com.ether.pms.auth.service.RoleService; import com.ether.pms.auth.service.RoleService;
import com.ether.pms.common.ApiResponse; import com.ether.pms.common.ApiResponse;
import com.ether.pms.common.util.PaginationValidator;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import java.util.List;
import java.util.UUID; import java.util.UUID;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.UUID;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import com.ether.pms.common.util.PaginationValidator;
/** /**
* 角色管理REST接口控制器 * 角色管理REST接口控制器
* *
* <p>提供角色Role相关的HTTP API接口包括角色的增删改查权限分配用户关联等功能 * <p>提供角色Role相关的HTTP API接口包括角色的增删改查权限分配用户关联等功能 所有接口均遵循RESTful设计规范返回统一的ApiResponse格式
* 所有接口均遵循RESTful设计规范返回统一的ApiResponse格式</p> *
* <p>主要接口
* *
* <p>主要接口</p>
* <ul> * <ul>
* <li>GET /api/auth/roles - 查询所有角色</li> * <li>GET /api/auth/roles - 查询所有角色
* <li>GET /api/auth/roles/{id} - 根据ID查询角色</li> * <li>GET /api/auth/roles/{id} - 根据ID查询角色
* <li>GET /api/auth/roles/project/{projectId} - 根据项目ID查询角色</li> * <li>GET /api/auth/roles/project/{projectId} - 根据项目ID查询角色
* <li>POST /api/auth/roles - 创建角色</li> * <li>POST /api/auth/roles - 创建角色
* <li>PUT /api/auth/roles/{id} - 更新角色</li> * <li>PUT /api/auth/roles/{id} - 更新角色
* <li>DELETE /api/auth/roles/{id} - 删除角色</li> * <li>DELETE /api/auth/roles/{id} - 删除角色
* <li>POST /api/auth/roles/{id}/permissions - 为角色分配权限</li> * <li>POST /api/auth/roles/{id}/permissions - 为角色分配权限
* <li>GET /api/auth/roles/{id}/permissions - 获取角色的权限</li> * <li>GET /api/auth/roles/{id}/permissions - 获取角色的权限
* <li>GET /api/auth/roles/{id}/users - 获取拥有某角色的用户</li> * <li>GET /api/auth/roles/{id}/users - 获取拥有某角色的用户
* </ul> * </ul>
* *
* @author Ether开发团队 * @author Ether开发团队
@ -82,7 +79,7 @@ public class RoleController {
/** /**
* 根据项目ID查询角色列表 * 根据项目ID查询角色列表
* *
* <p>获取指定项目下的所有角色</p> * <p>获取指定项目下的所有角色
* *
* @param projectId 项目ID * @param projectId 项目ID
* @return 包含该项目角色列表的响应 * @return 包含该项目角色列表的响应
@ -95,7 +92,7 @@ public class RoleController {
/** /**
* 创建新角色 * 创建新角色
* *
* <p>创建一个新的角色记录审计日志</p> * <p>创建一个新的角色记录审计日志
* *
* @param role 要创建的角色信息请求体 * @param role 要创建的角色信息请求体
* @return 包含创建后角色信息的响应 * @return 包含创建后角色信息的响应
@ -109,7 +106,7 @@ public class RoleController {
/** /**
* 更新角色信息 * 更新角色信息
* *
* <p>更新指定角色的信息记录审计日志</p> * <p>更新指定角色的信息记录审计日志
* *
* @param id 要更新的角色ID * @param id 要更新的角色ID
* @param role 包含新数据的角色信息请求体 * @param role 包含新数据的角色信息请求体
@ -117,14 +114,15 @@ public class RoleController {
*/ */
@PutMapping("/{id}") @PutMapping("/{id}")
@OperationLog(operation = "更新角色", module = "ROLE", action = AuditLog.ActionType.UPDATE) @OperationLog(operation = "更新角色", module = "ROLE", action = AuditLog.ActionType.UPDATE)
public ResponseEntity<ApiResponse<Role>> update(@PathVariable UUID id, @Valid @RequestBody Role role) { public ResponseEntity<ApiResponse<Role>> update(
@PathVariable UUID id, @Valid @RequestBody Role role) {
return ResponseEntity.ok(ApiResponse.success(roleService.update(id, role))); return ResponseEntity.ok(ApiResponse.success(roleService.update(id, role)));
} }
/** /**
* 删除角色 * 删除角色
* *
* <p>删除指定的角色记录审计日志</p> * <p>删除指定的角色记录审计日志
* *
* @param id 要删除的角色ID * @param id 要删除的角色ID
* @return 空响应表示操作成功 * @return 空响应表示操作成功
@ -139,7 +137,7 @@ public class RoleController {
/** /**
* 为角色分配权限 * 为角色分配权限
* *
* <p>替换角色原有的所有权限记录审计日志</p> * <p>替换角色原有的所有权限记录审计日志
* *
* @param id 角色ID * @param id 角色ID
* @param permissionIds 要分配的权限ID列表请求体 * @param permissionIds 要分配的权限ID列表请求体
@ -148,8 +146,7 @@ public class RoleController {
@PostMapping("/{id}/permissions") @PostMapping("/{id}/permissions")
@OperationLog(operation = "分配权限", module = "ROLE", action = AuditLog.ActionType.ASSIGN) @OperationLog(operation = "分配权限", module = "ROLE", action = AuditLog.ActionType.ASSIGN)
public ResponseEntity<ApiResponse<Void>> assignPermissions( public ResponseEntity<ApiResponse<Void>> assignPermissions(
@PathVariable UUID id, @PathVariable UUID id, @Valid @RequestBody List<UUID> permissionIds) {
@Valid @RequestBody List<UUID> permissionIds) {
roleService.assignPermissions(id, permissionIds); roleService.assignPermissions(id, permissionIds);
return ResponseEntity.ok(ApiResponse.success()); return ResponseEntity.ok(ApiResponse.success());
} }
@ -157,7 +154,7 @@ public class RoleController {
/** /**
* 获取角色的所有权限 * 获取角色的所有权限
* *
* <p>查询指定角色关联的所有权限</p> * <p>查询指定角色关联的所有权限
* *
* @param id 角色ID * @param id 角色ID
* @return 包含角色权限列表的响应 * @return 包含角色权限列表的响应
@ -170,7 +167,7 @@ public class RoleController {
/** /**
* 获取拥有某角色的所有用户 * 获取拥有某角色的所有用户
* *
* <p>查询所有拥有指定角色ID的用户</p> * <p>查询所有拥有指定角色ID的用户
* *
* @param id 角色ID * @param id 角色ID
* @return 包含用户列表的响应 * @return 包含用户列表的响应

View File

@ -7,19 +7,15 @@ import com.ether.pms.auth.service.SysConfigService;
import com.ether.pms.common.ApiResponse; import com.ether.pms.common.ApiResponse;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import java.util.Map;
import lombok.Data; import lombok.Data;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import java.util.Map;
import java.util.UUID;
/** /**
* 系统配置控制器 * 系统配置控制器 提供系统配置的 RESTful API 接口 包括配置的查询更新等操作
* 提供系统配置的 RESTful API 接口
* 包括配置的查询更新等操作
* *
* @author Ether Team * @author Ether Team
* @since 1.0.0 * @since 1.0.0
@ -30,14 +26,11 @@ import java.util.UUID;
@Validated @Validated
public class SysConfigController { public class SysConfigController {
/** /** 系统配置服务对象 */
* 系统配置服务对象
*/
private final SysConfigService sysConfigService; private final SysConfigService sysConfigService;
/** /**
* 获取所有配置项 * 获取所有配置项 返回系统中所有配置的键值对
* 返回系统中所有配置的键值对
* *
* @return 配置键值对 Map * @return 配置键值对 Map
*/ */
@ -58,8 +51,7 @@ public class SysConfigController {
} }
/** /**
* 更新单个配置项 * 更新单个配置项 根据配置键更新对应的配置值并记录审计日志
* 根据配置键更新对应的配置值并记录审计日志
* *
* @param configKey 配置键 * @param configKey 配置键
* @param request 更新请求对象包含新的配置值 * @param request 更新请求对象包含新的配置值
@ -68,33 +60,29 @@ public class SysConfigController {
@PutMapping("/{configKey}") @PutMapping("/{configKey}")
@OperationLog(operation = "更新系统设置", module = "SYSTEM", action = AuditLog.ActionType.UPDATE) @OperationLog(operation = "更新系统设置", module = "SYSTEM", action = AuditLog.ActionType.UPDATE)
public ResponseEntity<ApiResponse<SysConfig>> updateConfig( public ResponseEntity<ApiResponse<SysConfig>> updateConfig(
@PathVariable String configKey, @PathVariable String configKey, @Valid @RequestBody ConfigUpdateRequest request) {
@Valid @RequestBody ConfigUpdateRequest request) { return ResponseEntity.ok(
return ResponseEntity.ok(ApiResponse.success(sysConfigService.updateConfig(configKey, request.getConfigValue()))); ApiResponse.success(
sysConfigService.updateConfig(configKey, request.getConfigValue())));
} }
/** /**
* 批量更新配置项 * 批量更新配置项 同时更新多个配置键值对并记录审计日志
* 同时更新多个配置键值对并记录审计日志
* *
* @param configs 配置键值对 Map * @param configs 配置键值对 Map
* @return 更新后的配置键值对 Map * @return 更新后的配置键值对 Map
*/ */
@PutMapping @PutMapping
@OperationLog(operation = "更新系统设置", module = "SYSTEM", action = AuditLog.ActionType.UPDATE) @OperationLog(operation = "更新系统设置", module = "SYSTEM", action = AuditLog.ActionType.UPDATE)
public ResponseEntity<ApiResponse<Map<String, String>>> updateConfigs(@RequestBody Map<String, String> configs) { public ResponseEntity<ApiResponse<Map<String, String>>> updateConfigs(
@RequestBody Map<String, String> configs) {
return ResponseEntity.ok(ApiResponse.success(sysConfigService.updateConfigs(configs))); return ResponseEntity.ok(ApiResponse.success(sysConfigService.updateConfigs(configs)));
} }
/** /** 配置更新请求对象 用于接收单个配置项的更新请求 */
* 配置更新请求对象
* 用于接收单个配置项的更新请求
*/
@Data @Data
public static class ConfigUpdateRequest { public static class ConfigUpdateRequest {
/** /** 新的配置值 */
* 新的配置值
*/
@NotBlank(message = "配置值不能为空") @NotBlank(message = "配置值不能为空")
private String configValue; private String configValue;
} }

View File

@ -11,29 +11,27 @@ import com.ether.pms.auth.service.UserProjectService;
import com.ether.pms.auth.service.UserService; import com.ether.pms.auth.service.UserService;
import com.ether.pms.common.ApiResponse; import com.ether.pms.common.ApiResponse;
import com.ether.pms.common.util.BatchOperationValidator; import com.ether.pms.common.util.BatchOperationValidator;
import com.ether.pms.common.util.PaginationValidator;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size; import jakarta.validation.constraints.Size;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
import lombok.Data; import lombok.Data;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import com.ether.pms.common.util.PaginationValidator;
/** /**
* 用户管理控制器 * 用户管理控制器
* *
* <p>提供用户相关的RESTful API接口包括用户CRUD密码管理角色分配项目关联等功能</p> * <p>提供用户相关的RESTful API接口包括用户CRUD密码管理角色分配项目关联等功能
* *
* <p>所有接口路径前缀为/api/auth/users使用JSON格式进行数据交互</p> * <p>所有接口路径前缀为/api/auth/users使用JSON格式进行数据交互
* *
* @author Ether开发团队 * @author Ether开发团队
* @version 1.0.0 * @version 1.0.0
@ -47,15 +45,17 @@ public class UserController {
/** 用户服务 */ /** 用户服务 */
private final UserService userService; private final UserService userService;
/** 用户项目关联服务 */ /** 用户项目关联服务 */
private final UserProjectService userProjectService; private final UserProjectService userProjectService;
/** 用户管理服务 */ /** 用户管理服务 */
private final UserManagementService userManagementService; private final UserManagementService userManagementService;
/** /**
* 分页查询用户列表 * 分页查询用户列表
* *
* <p>返回系统中所有用户的信息包含关联的角色列表支持分页</p> * <p>返回系统中所有用户的信息包含关联的角色列表支持分页
* *
* @param page 页码从0开始默认为0 * @param page 页码从0开始默认为0
* @param size 每页大小默认为10 * @param size 每页大小默认为10
@ -72,7 +72,7 @@ public class UserController {
/** /**
* 根据ID获取用户信息 * 根据ID获取用户信息
* *
* <p>根据用户唯一标识符查询指定用户的详细信息</p> * <p>根据用户唯一标识符查询指定用户的详细信息
* *
* @param id 用户唯一标识符 * @param id 用户唯一标识符
* @return 包含用户信息的成功响应 * @return 包含用户信息的成功响应
@ -85,7 +85,7 @@ public class UserController {
/** /**
* 创建新用户 * 创建新用户
* *
* <p>创建一个新的用户账号需要提供用户名密码等基本信息</p> * <p>创建一个新的用户账号需要提供用户名密码等基本信息
* *
* @param user 待创建的用户信息 * @param user 待创建的用户信息
* @return 包含创建成功的用户信息的成功响应 * @return 包含创建成功的用户信息的成功响应
@ -99,7 +99,7 @@ public class UserController {
/** /**
* 更新用户信息 * 更新用户信息
* *
* <p>更新指定用户的个人信息如真实姓名手机号邮箱等</p> * <p>更新指定用户的个人信息如真实姓名手机号邮箱等
* *
* @param id 用户唯一标识符 * @param id 用户唯一标识符
* @param user 包含更新信息的用户对象 * @param user 包含更新信息的用户对象
@ -107,14 +107,15 @@ public class UserController {
*/ */
@PutMapping("/{id}") @PutMapping("/{id}")
@OperationLog(operation = "更新用户", module = "USER", action = AuditLog.ActionType.UPDATE) @OperationLog(operation = "更新用户", module = "USER", action = AuditLog.ActionType.UPDATE)
public ResponseEntity<ApiResponse<User>> update(@PathVariable UUID id, @Valid @RequestBody User user) { public ResponseEntity<ApiResponse<User>> update(
@PathVariable UUID id, @Valid @RequestBody User user) {
return ResponseEntity.ok(ApiResponse.success(userService.update(id, user))); return ResponseEntity.ok(ApiResponse.success(userService.update(id, user)));
} }
/** /**
* 删除用户 * 删除用户
* *
* <p>根据用户ID删除指定用户账号</p> * <p>根据用户ID删除指定用户账号
* *
* @param id 用户唯一标识符 * @param id 用户唯一标识符
* @return 成功响应 * @return 成功响应
@ -129,7 +130,7 @@ public class UserController {
/** /**
* 修改用户密码 * 修改用户密码
* *
* <p>用户修改自己的密码需要提供原密码和新密码</p> * <p>用户修改自己的密码需要提供原密码和新密码
* *
* @param id 用户唯一标识符 * @param id 用户唯一标识符
* @param request 包含原密码和新密码的请求对象 * @param request 包含原密码和新密码的请求对象
@ -138,8 +139,7 @@ public class UserController {
@PutMapping("/{id}/password") @PutMapping("/{id}/password")
@OperationLog(operation = "修改密码", module = "USER", action = AuditLog.ActionType.UPDATE) @OperationLog(operation = "修改密码", module = "USER", action = AuditLog.ActionType.UPDATE)
public ResponseEntity<ApiResponse<Void>> updatePassword( public ResponseEntity<ApiResponse<Void>> updatePassword(
@PathVariable UUID id, @PathVariable UUID id, @Valid @RequestBody PasswordRequest request) {
@Valid @RequestBody PasswordRequest request) {
userService.updatePassword(id, request.getOldPassword(), request.getNewPassword()); userService.updatePassword(id, request.getOldPassword(), request.getNewPassword());
return ResponseEntity.ok(ApiResponse.success()); return ResponseEntity.ok(ApiResponse.success());
} }
@ -147,7 +147,7 @@ public class UserController {
/** /**
* 分配用户角色 * 分配用户角色
* *
* <p>为指定用户分配一个或多个角色替换现有的角色</p> * <p>为指定用户分配一个或多个角色替换现有的角色
* *
* @param id 用户唯一标识符 * @param id 用户唯一标识符
* @param roleIds 要分配的角色ID列表 * @param roleIds 要分配的角色ID列表
@ -156,8 +156,7 @@ public class UserController {
@PostMapping("/{id}/roles") @PostMapping("/{id}/roles")
@OperationLog(operation = "分配角色", module = "USER", action = AuditLog.ActionType.ASSIGN) @OperationLog(operation = "分配角色", module = "USER", action = AuditLog.ActionType.ASSIGN)
public ResponseEntity<ApiResponse<Void>> assignRoles( public ResponseEntity<ApiResponse<Void>> assignRoles(
@PathVariable UUID id, @PathVariable UUID id, @Valid @RequestBody List<UUID> roleIds) {
@Valid @RequestBody List<UUID> roleIds) {
BatchOperationValidator.validateAssignmentSize(roleIds.size()); BatchOperationValidator.validateAssignmentSize(roleIds.size());
userService.assignRoles(id, roleIds); userService.assignRoles(id, roleIds);
return ResponseEntity.ok(ApiResponse.success()); return ResponseEntity.ok(ApiResponse.success());
@ -166,7 +165,7 @@ public class UserController {
/** /**
* 获取用户参与的项目列表 * 获取用户参与的项目列表
* *
* <p>查询指定用户参与的所有项目及其在项目中的角色</p> * <p>查询指定用户参与的所有项目及其在项目中的角色
* *
* @param id 用户唯一标识符 * @param id 用户唯一标识符
* @return 包含用户参与项目列表的成功响应 * @return 包含用户参与项目列表的成功响应
@ -179,7 +178,7 @@ public class UserController {
/** /**
* 添加用户到项目 * 添加用户到项目
* *
* <p>将指定用户添加到某个项目中并设置用户在项目中的角色</p> * <p>将指定用户添加到某个项目中并设置用户在项目中的角色
* *
* @param id 用户唯一标识符 * @param id 用户唯一标识符
* @param request 包含项目ID和角色信息的请求对象 * @param request 包含项目ID和角色信息的请求对象
@ -187,8 +186,7 @@ public class UserController {
*/ */
@PostMapping("/{id}/projects") @PostMapping("/{id}/projects")
public ResponseEntity<ApiResponse<Void>> addUserToProject( public ResponseEntity<ApiResponse<Void>> addUserToProject(
@PathVariable UUID id, @PathVariable UUID id, @Valid @RequestBody UserProjectRequest request) {
@Valid @RequestBody UserProjectRequest request) {
userProjectService.addUserToProject(id, request.getProjectId(), request.getRoleInProject()); userProjectService.addUserToProject(id, request.getProjectId(), request.getRoleInProject());
return ResponseEntity.ok(ApiResponse.success()); return ResponseEntity.ok(ApiResponse.success());
} }
@ -196,7 +194,7 @@ public class UserController {
/** /**
* 从项目中移除用户 * 从项目中移除用户
* *
* <p>将指定用户从某个项目中移除删除用户与项目的关联关系</p> * <p>将指定用户从某个项目中移除删除用户与项目的关联关系
* *
* @param id 用户唯一标识符 * @param id 用户唯一标识符
* @param projectId 项目唯一标识符 * @param projectId 项目唯一标识符
@ -204,8 +202,7 @@ public class UserController {
*/ */
@DeleteMapping("/{id}/projects/{projectId}") @DeleteMapping("/{id}/projects/{projectId}")
public ResponseEntity<ApiResponse<Void>> removeUserFromProject( public ResponseEntity<ApiResponse<Void>> removeUserFromProject(
@PathVariable UUID id, @PathVariable UUID id, @PathVariable UUID projectId) {
@PathVariable UUID projectId) {
userProjectService.removeUserFromProject(id, projectId); userProjectService.removeUserFromProject(id, projectId);
return ResponseEntity.ok(ApiResponse.success()); return ResponseEntity.ok(ApiResponse.success());
} }
@ -213,20 +210,22 @@ public class UserController {
/** /**
* 获取企业员工列表用于项目管理添加成员 * 获取企业员工列表用于项目管理添加成员
* *
* <p>返回所有企业员工的用户信息列表</p> * <p>返回所有企业员工的用户信息列表
* *
* @return 包含企业员工列表的成功响应 * @return 包含企业员工列表的成功响应
*/ */
@GetMapping("/enterprise") @GetMapping("/enterprise")
public ResponseEntity<ApiResponse<List<UserVO>>> getEnterpriseUsers() { public ResponseEntity<ApiResponse<List<UserVO>>> getEnterpriseUsers() {
List<User> users = userManagementService.findEnterpriseUsers(); List<User> users = userManagementService.findEnterpriseUsers();
return ResponseEntity.ok(ApiResponse.success(users.stream().map(UserVO::fromEntity).collect(Collectors.toList()))); return ResponseEntity.ok(
ApiResponse.success(
users.stream().map(UserVO::fromEntity).collect(Collectors.toList())));
} }
/** /**
* 密码修改请求对象 * 密码修改请求对象
* *
* <p>包含修改密码所需的原密码和新密码</p> * <p>包含修改密码所需的原密码和新密码
*/ */
@Data @Data
public static class PasswordRequest { public static class PasswordRequest {

View File

@ -1,15 +1,14 @@
package com.ether.pms.auth.controller.dto; package com.ether.pms.auth.controller.dto;
import jakarta.validation.constraints.NotNull;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import jakarta.validation.constraints.NotNull;
import lombok.Data; import lombok.Data;
/** /**
* 添加项目成员请求DTO * 添加项目成员请求DTO
* *
* <p>用于接收将用户添加到项目的请求数据</p> * <p>用于接收将用户添加到项目的请求数据
* *
* @author Ether开发团队 * @author Ether开发团队
* @version 1.0.0 * @version 1.0.0
@ -18,10 +17,7 @@ import lombok.Data;
@Data @Data
public class AddProjectMemberDTO { public class AddProjectMemberDTO {
/** /** 用户ID 要添加到项目的用户唯一标识符 */
* 用户ID
* 要添加到项目的用户唯一标识符
*/
@NotNull(message = "用户ID不能为空") @NotNull(message = "用户ID不能为空")
private UUID userId; private UUID userId;
@ -31,9 +27,6 @@ public class AddProjectMemberDTO {
*/ */
private String staffType; private String staffType;
/** /** 角色ID列表 可选参数分配的角色ID列表 */
* 角色ID列表
* 可选参数分配的角色ID列表
*/
private List<UUID> roleIds; private List<UUID> roleIds;
} }

View File

@ -1,15 +1,14 @@
package com.ether.pms.auth.controller.dto; package com.ether.pms.auth.controller.dto;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.UUID; import java.util.UUID;
import lombok.Data;
/** /**
* 创建企业用户请求DTO * 创建企业用户请求DTO
* *
* <p>用于接收创建企业用户的请求数据包含用户基础信息和企业扩展信息</p> * <p>用于接收创建企业用户的请求数据包含用户基础信息和企业扩展信息
* *
* @author Ether开发团队 * @author Ether开发团队
* @version 1.0.0 * @version 1.0.0
@ -18,65 +17,35 @@ import java.util.UUID;
@Data @Data
public class CreateEnterpriseUserDTO { public class CreateEnterpriseUserDTO {
/** /** 用户名 用于登录系统 */
* 用户名
* 用于登录系统
*/
@NotBlank(message = "用户名不能为空") @NotBlank(message = "用户名不能为空")
private String username; private String username;
/** /** 密码 用户登录密码 */
* 密码
* 用户登录密码
*/
@NotBlank(message = "密码不能为空") @NotBlank(message = "密码不能为空")
private String password; private String password;
/** /** 真实姓名 用户的真实姓名 */
* 真实姓名
* 用户的真实姓名
*/
private String realName; private String realName;
/** /** 手机号码 中国大陆手机号 */
* 手机号码
* 中国大陆手机号
*/
private String phone; private String phone;
/** /** 电子邮箱 用户的邮箱地址 */
* 电子邮箱
* 用户的邮箱地址
*/
private String email; private String email;
/** /** 员工工号 企业内部员工编号 */
* 员工工号
* 企业内部员工编号
*/
private String employeeNo; private String employeeNo;
/** /** 部门ID 用户所属的部门标识符 */
* 部门ID
* 用户所属的部门标识符
*/
private UUID deptId; private UUID deptId;
/** /** 职位 用户在企业中的职位 */
* 职位
* 用户在企业中的职位
*/
private String position; private String position;
/** /** 入职日期 员工加入企业的日期 */
* 入职日期
* 员工加入企业的日期
*/
private LocalDate entryDate; private LocalDate entryDate;
/** /** 员工类别 标识员工类型ENTERPRISE普通员工、MANAGEMENT管理层 */
* 员工类别
* 标识员工类型ENTERPRISE普通员工MANAGEMENT管理层
*/
private String userCategory; private String userCategory;
} }

View File

@ -1,23 +0,0 @@
package com.ether.pms.auth.controller.dto;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.util.UUID;
@Data
public class DataAccessRequest {
@NotBlank(message = "数据类型不能为空")
private String dataType;
@NotNull(message = "数据ID不能为空")
private UUID dataId;
@NotBlank(message = "访问类型不能为空")
private String accessType;
private UUID accessId;
@NotBlank(message = "访问级别不能为空")
private String accessLevel = "read";
}

View File

@ -1,14 +1,13 @@
package com.ether.pms.auth.controller.dto; package com.ether.pms.auth.controller.dto;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import java.util.UUID; import java.util.UUID;
import lombok.Data;
/** /**
* 部门数据传输对象 * 部门数据传输对象
* *
* <p>用于接收创建或更新部门的请求数据</p> * <p>用于接收创建或更新部门的请求数据
* *
* @author Ether开发团队 * @author Ether开发团队
* @version 1.0.0 * @version 1.0.0
@ -17,35 +16,20 @@ import java.util.UUID;
@Data @Data
public class DeptDTO { public class DeptDTO {
/** /** 部门名称 */
* 部门名称
*/
@NotBlank(message = "部门名称不能为空") @NotBlank(message = "部门名称不能为空")
private String deptName; private String deptName;
/** /** 上级部门ID */
* 上级部门ID
*/
private UUID parentId; private UUID parentId;
/** /** 部门类型 ADMIN: 行政管理 ENGINEERING: 工程部 SECURITY: 安保部 CS: 客服部 CLEANING: 保洁部 */
* 部门类型
* ADMIN: 行政管理
* ENGINEERING: 工程部
* SECURITY: 安保部
* CS: 客服部
* CLEANING: 保洁部
*/
private String deptType = "ADMIN"; private String deptType = "ADMIN";
/** /** 部门负责人ID */
* 部门负责人ID
*/
private UUID leaderId; private UUID leaderId;
/** /** 排序序号 */
* 排序序号
*/
private Integer sortOrder; private Integer sortOrder;
private String status = "ACTIVE"; private String status = "ACTIVE";

View File

@ -1,16 +1,15 @@
package com.ether.pms.auth.controller.dto; package com.ether.pms.auth.controller.dto;
import com.ether.pms.auth.entity.Dept; import com.ether.pms.auth.entity.Dept;
import lombok.Data;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import lombok.Data;
/** /**
* 部门视图对象 * 部门视图对象
* *
* <p>用于返回部门信息的视图对象包含部门基本信息和子部门列表</p> * <p>用于返回部门信息的视图对象包含部门基本信息和子部门列表
* *
* @author Ether开发团队 * @author Ether开发团队
* @version 1.0.0 * @version 1.0.0
@ -19,49 +18,31 @@ import java.util.UUID;
@Data @Data
public class DeptVO { public class DeptVO {
/** /** 部门唯一标识符 */
* 部门唯一标识符
*/
private UUID id; private UUID id;
/** /** 上级部门ID */
* 上级部门ID
*/
private UUID parentId; private UUID parentId;
/** /** 部门名称 */
* 部门名称
*/
private String deptName; private String deptName;
/** /** 部门类型 */
* 部门类型
*/
private String deptType; private String deptType;
/** /** 部门类型描述 */
* 部门类型描述
*/
private String deptTypeDesc; private String deptTypeDesc;
/** /** 部门负责人ID */
* 部门负责人ID
*/
private UUID leaderId; private UUID leaderId;
/** /** 排序序号 */
* 排序序号
*/
private Integer sortOrder; private Integer sortOrder;
/** /** 部门状态 */
* 部门状态
*/
private String status; private String status;
/** /** 子部门列表 */
* 子部门列表
*/
private List<DeptVO> children = new ArrayList<>(); private List<DeptVO> children = new ArrayList<>();
/** /**

View File

@ -1,15 +1,14 @@
package com.ether.pms.auth.controller.dto; package com.ether.pms.auth.controller.dto;
import java.util.List;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import java.util.List;
/** /**
* 分页响应DTO * 分页响应DTO
* *
* <p>用于返回分页数据的统一格式包含内容列表分页信息和总数</p> * <p>用于返回分页数据的统一格式包含内容列表分页信息和总数
* *
* @author Ether开发团队 * @author Ether开发团队
* @version 1.0.0 * @version 1.0.0
@ -20,24 +19,16 @@ import java.util.List;
@AllArgsConstructor @AllArgsConstructor
public class PageResponse<T> { public class PageResponse<T> {
/** /** 分页数据内容列表 */
* 分页数据内容列表
*/
private List<T> content; private List<T> content;
/** /** 当前页数据条数 */
* 当前页数据条数
*/
private int size; private int size;
/** /** 当前页码 */
* 当前页码
*/
private int page; private int page;
/** /** 数据总条数 */
* 数据总条数
*/
private long totalElements; private long totalElements;
/** /**

View File

@ -2,18 +2,16 @@ package com.ether.pms.auth.controller.dto;
import com.ether.pms.auth.entity.ProjectStaff; import com.ether.pms.auth.entity.ProjectStaff;
import com.ether.pms.auth.entity.ProjectStaffRole; import com.ether.pms.auth.entity.ProjectStaffRole;
import lombok.Data;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import java.util.stream.Collectors; import lombok.Data;
/** /**
* 项目成员视图对象 * 项目成员视图对象
* *
* <p>用于返回项目成员信息包含用户基本信息角色信息和加入时间</p> * <p>用于返回项目成员信息包含用户基本信息角色信息和加入时间
* *
* @author Ether开发团队 * @author Ether开发团队
* @version 1.0.0 * @version 1.0.0
@ -22,59 +20,37 @@ import java.util.stream.Collectors;
@Data @Data
public class ProjectMemberVO { public class ProjectMemberVO {
/** /** 用户唯一标识符 */
* 用户唯一标识符
*/
private UUID id; private UUID id;
/** /** 用户名 */
* 用户名
*/
private String username; private String username;
/** /** 真实姓名 */
* 真实姓名
*/
private String realName; private String realName;
/** /** 手机号 */
* 手机号
*/
private String phone; private String phone;
/** /** 邮箱 */
* 邮箱
*/
private String email; private String email;
/** /** 用户类型 */
* 用户类型
*/
private String userType; private String userType;
/** /** 用户状态 */
* 用户状态
*/
private String status; private String status;
/** /** 员工类型 */
* 员工类型
*/
private String staffType; private String staffType;
/** /** 项目中的角色列表 */
* 项目中的角色列表
*/
private List<String> roles; private List<String> roles;
/** /** 角色名称列表(用于显示) */
* 角色名称列表用于显示
*/
private String roleNames; private String roleNames;
/** /** 加入时间 */
* 加入时间
*/
private LocalDateTime createdAt; private LocalDateTime createdAt;
/** /**
@ -91,7 +67,8 @@ public class ProjectMemberVO {
vo.setPhone(staff.getUser().getPhone()); vo.setPhone(staff.getUser().getPhone());
vo.setEmail(staff.getUser().getEmail()); vo.setEmail(staff.getUser().getEmail());
vo.setUserType(staff.getUser().getUserType()); vo.setUserType(staff.getUser().getUserType());
vo.setStatus(staff.getUser().getStatus() != null ? staff.getUser().getStatus().name() : null); vo.setStatus(
staff.getUser().getStatus() != null ? staff.getUser().getStatus().name() : null);
vo.setStaffType(staff.getStaffType()); vo.setStaffType(staff.getStaffType());
vo.setCreatedAt(staff.getCreatedAt()); vo.setCreatedAt(staff.getCreatedAt());

View File

@ -2,12 +2,10 @@ package com.ether.pms.auth.controller.dto;
import com.ether.pms.auth.entity.Role; import com.ether.pms.auth.entity.Role;
import com.ether.pms.auth.entity.User; import com.ether.pms.auth.entity.User;
import lombok.Data;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import java.util.stream.Collectors; import lombok.Data;
@Data @Data
public class RoleWithUsersDTO { public class RoleWithUsersDTO {

View File

@ -2,8 +2,8 @@ package com.ether.pms.auth.controller.dto;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.util.UUID; import java.util.UUID;
import lombok.Data;
@Data @Data
public class UserProjectRequest { public class UserProjectRequest {

View File

@ -1,16 +1,15 @@
package com.ether.pms.auth.controller.dto; package com.ether.pms.auth.controller.dto;
import com.ether.pms.auth.entity.User; import com.ether.pms.auth.entity.User;
import lombok.Data;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import lombok.Data;
/** /**
* 用户视图对象 * 用户视图对象
* *
* <p>用于返回用户信息的简化视图包含用户基本信息部门信息和角色列表</p> * <p>用于返回用户信息的简化视图包含用户基本信息部门信息和角色列表
* *
* @author Ether开发团队 * @author Ether开发团队
* @version 1.0.0 * @version 1.0.0
@ -19,64 +18,40 @@ import java.util.stream.Collectors;
@Data @Data
public class UserVO { public class UserVO {
/** /** 用户唯一标识符 */
* 用户唯一标识符
*/
private UUID id; private UUID id;
/** /** 用户名 */
* 用户名
*/
private String username; private String username;
/** /** 真实姓名 */
* 真实姓名
*/
private String realName; private String realName;
/** /** 手机号 */
* 手机号
*/
private String phone; private String phone;
/** /** 邮箱 */
* 邮箱
*/
private String email; private String email;
/** /** 用户类型 */
* 用户类型
*/
private String userType; private String userType;
/** /** 用户状态 */
* 用户状态
*/
private String status; private String status;
/** /** 部门ID */
* 部门ID
*/
private UUID deptId; private UUID deptId;
/** /** 部门名称 */
* 部门名称
*/
private String deptName; private String deptName;
/** /** 角色列表 */
* 角色列表
*/
private List<RoleInfo> roles; private List<RoleInfo> roles;
/** /** 角色名称列表(逗号分隔) */
* 角色名称列表逗号分隔
*/
private String roleNames; private String roleNames;
/** /** 角色信息内部类 */
* 角色信息内部类
*/
@Data @Data
public static class RoleInfo { public static class RoleInfo {
private UUID id; private UUID id;
@ -103,8 +78,10 @@ public class UserVO {
// 转换角色信息 // 转换角色信息
if (user.getRoles() != null && !user.getRoles().isEmpty()) { if (user.getRoles() != null && !user.getRoles().isEmpty()) {
List<RoleInfo> roleInfos = user.getRoles().stream() List<RoleInfo> roleInfos =
.map(role -> { user.getRoles().stream()
.map(
role -> {
RoleInfo info = new RoleInfo(); RoleInfo info = new RoleInfo();
info.setId(role.getId()); info.setId(role.getId());
info.setCode(role.getCode()); info.setCode(role.getCode());
@ -113,7 +90,8 @@ public class UserVO {
}) })
.collect(Collectors.toList()); .collect(Collectors.toList());
vo.setRoles(roleInfos); vo.setRoles(roleInfos);
vo.setRoleNames(roleInfos.stream().map(RoleInfo::getName).collect(Collectors.joining(""))); vo.setRoleNames(
roleInfos.stream().map(RoleInfo::getName).collect(Collectors.joining("")));
} }
return vo; return vo;

View File

@ -2,12 +2,11 @@ package com.ether.pms.auth.controller.dto;
import com.ether.pms.auth.entity.Role; import com.ether.pms.auth.entity.Role;
import com.ether.pms.auth.entity.User; import com.ether.pms.auth.entity.User;
import lombok.Data;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import lombok.Data;
@Data @Data
public class UserWithRolesDTO { public class UserWithRolesDTO {
@ -51,9 +50,8 @@ public class UserWithRolesDTO {
dto.setLastLoginIp(user.getLastLoginIp()); dto.setLastLoginIp(user.getLastLoginIp());
dto.setCreatedAt(user.getCreatedAt()); dto.setCreatedAt(user.getCreatedAt());
if (user.getRoles() != null) { if (user.getRoles() != null) {
dto.setRoles(user.getRoles().stream() dto.setRoles(
.map(RoleInfo::fromRole) user.getRoles().stream().map(RoleInfo::fromRole).collect(Collectors.toList()));
.collect(Collectors.toList()));
} }
return dto; return dto;
} }

View File

@ -1,22 +1,23 @@
package com.ether.pms.auth.entity; package com.ether.pms.auth.entity;
import jakarta.persistence.*; import jakarta.persistence.*;
import java.time.LocalDateTime;
import java.util.UUID;
import lombok.Data; import lombok.Data;
import org.hibernate.annotations.CreationTimestamp; import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.JdbcTypeCode; import org.hibernate.annotations.JdbcTypeCode;
import org.hibernate.type.SqlTypes; import org.hibernate.type.SqlTypes;
import java.time.LocalDateTime;
import java.util.UUID;
@Entity @Entity
@Table(name = "sys_audit_log", indexes = { @Table(
name = "sys_audit_log",
indexes = {
@Index(name = "idx_audit_log_created_at", columnList = "createdAt"), @Index(name = "idx_audit_log_created_at", columnList = "createdAt"),
@Index(name = "idx_audit_log_user_id", columnList = "userId"), @Index(name = "idx_audit_log_user_id", columnList = "userId"),
@Index(name = "idx_audit_log_module", columnList = "module"), @Index(name = "idx_audit_log_module", columnList = "module"),
@Index(name = "idx_audit_log_action", columnList = "action"), @Index(name = "idx_audit_log_action", columnList = "action"),
@Index(name = "idx_al_user_createdat", columnList = "user_id, createdAt DESC") @Index(name = "idx_al_user_createdat", columnList = "user_id, createdAt DESC")
}) })
@Data @Data
public class AuditLog { public class AuditLog {

View File

@ -1,45 +0,0 @@
package com.ether.pms.auth.entity;
import jakarta.persistence.*;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.UUID;
@Entity
@Table(name = "biz_data_access")
@Data
@Deprecated
public class DataAccess {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID id;
@Column(name = "data_type", nullable = false)
private String dataType;
@Column(name = "data_id", nullable = false)
private UUID dataId;
@Column(name = "access_type", nullable = false)
private String accessType;
@Column(name = "access_id", nullable = false)
private UUID accessId;
@Column(name = "access_level", nullable = false)
private String accessLevel = "read";
@Column(name = "granted_by")
private UUID grantedBy;
@Column(name = "granted_at", nullable = false)
private LocalDateTime grantedAt = LocalDateTime.now();
@PrePersist
public void prePersist() {
if (this.grantedAt == null) {
this.grantedAt = LocalDateTime.now();
}
}
}

View File

@ -1,23 +1,19 @@
package com.ether.pms.auth.entity; package com.ether.pms.auth.entity;
import jakarta.persistence.*; import jakarta.persistence.*;
import lombok.Data;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.UUID; import java.util.UUID;
import lombok.Data;
/** /**
* 部门实体类 * 部门实体类
* *
* <p>表示组织架构中的部门信息包含部门名称类型负责人等</p> * <p>表示组织架构中的部门信息包含部门名称类型负责人等
* *
* <p>支持树形结构通过parentId指向上级部门</p> * <p>支持树形结构通过parentId指向上级部门
* *
* <p>部门类型用于区分不同业务职能的部门 * <p>部门类型用于区分不同业务职能的部门 - ADMIN: 行政管理部门如总部行政部财务部人力资源部等 - ENGINEERING: 工程部门负责设施设备维护维修等 -
* - ADMIN: 行政管理部门如总部行政部财务部人力资源部等 * SECURITY: 安保部门负责安全保卫门禁管理等 - CS: 客服部门负责业主服务投诉处理等 - CLEANING: 保洁部门负责清洁卫生绿化养护等
* - ENGINEERING: 工程部门负责设施设备维护维修等
* - SECURITY: 安保部门负责安全保卫门禁管理等
* - CS: 客服部门负责业主服务投诉处理等
* - CLEANING: 保洁部门负责清洁卫生绿化养护等</p>
* *
* @author Ether开发团队 * @author Ether开发团队
* @version 1.0.0 * @version 1.0.0
@ -28,77 +24,47 @@ import java.util.UUID;
@Data @Data
public class Dept { public class Dept {
/** /** 部门唯一标识符 采用UUID策略自动生成 */
* 部门唯一标识符
* 采用UUID策略自动生成
*/
@Id @Id
@GeneratedValue(strategy = GenerationType.UUID) @GeneratedValue(strategy = GenerationType.UUID)
private UUID id; private UUID id;
/** /** 上级部门ID 指向父部门的ID用于构建部门树形结构顶级部门此字段为空 */
* 上级部门ID
* 指向父部门的ID用于构建部门树形结构顶级部门此字段为空
*/
private UUID parentId; private UUID parentId;
/** /** 部门名称 部门的显示名称 */
* 部门名称
* 部门的显示名称
*/
@Column(nullable = false, length = 100) @Column(nullable = false, length = 100)
private String deptName; private String deptName;
/** /**
* 部门类型 * 部门类型 标识部门所属的业务类型用于区分不同职能的部门 ADMIN: 行政管理 - 负责公司行政财务人事等管理职能 ENGINEERING: 工程部 -
* 标识部门所属的业务类型用于区分不同职能的部门 * 负责设施设备维护维修保养等技术工作 SECURITY: 安保部 - 负责安全保卫门禁管理巡逻等工作 CS: 客服部 - 负责业主服务投诉处理满意度调查等 CLEANING:
* ADMIN: 行政管理 - 负责公司行政财务人事等管理职能 * 保洁部 - 负责清洁卫生绿化养护垃圾处理等
* ENGINEERING: 工程部 - 负责设施设备维护维修保养等技术工作
* SECURITY: 安保部 - 负责安全保卫门禁管理巡逻等工作
* CS: 客服部 - 负责业主服务投诉处理满意度调查等
* CLEANING: 保洁部 - 负责清洁卫生绿化养护垃圾处理等
*/ */
@Column(length = 20) @Column(length = 20)
private String deptType = "ADMIN"; private String deptType = "ADMIN";
/** /** 部门负责人ID 部门负责人的用户ID */
* 部门负责人ID
* 部门负责人的用户ID
*/
private UUID leaderId; private UUID leaderId;
/** /** 排序序号 部门在同一级别中的排序顺序,数字越小越靠前 */
* 排序序号
* 部门在同一级别中的排序顺序数字越小越靠前
*/
private Integer sortOrder; private Integer sortOrder;
/** /** 部门状态 标识部门的状态ACTIVE-正常、DISABLED-停用 */
* 部门状态
* 标识部门的状态ACTIVE-正常DISABLED-停用
*/
@Column(length = 20) @Column(length = 20)
private String status = "ACTIVE"; private String status = "ACTIVE";
/** /** 创建时间 */
* 创建时间
*/
private LocalDateTime createdAt; private LocalDateTime createdAt;
/** /** 更新时间 */
* 更新时间
*/
private LocalDateTime updatedAt; private LocalDateTime updatedAt;
/** /** 创建人ID */
* 创建人ID
*/
@Column(name = "created_by") @Column(name = "created_by")
private UUID createdBy; private UUID createdBy;
/** /** 更新人ID */
* 更新人ID
*/
@Column(name = "updated_by") @Column(name = "updated_by")
private UUID updatedBy; private UUID updatedBy;
@ -113,9 +79,7 @@ public class Dept {
updatedAt = LocalDateTime.now(); updatedAt = LocalDateTime.now();
} }
/** /** 部门类型枚举 */
* 部门类型枚举
*/
public enum DeptType { public enum DeptType {
ADMIN("行政管理"), ADMIN("行政管理"),
ENGINEERING("工程部"), ENGINEERING("工程部"),

View File

@ -1,16 +1,16 @@
package com.ether.pms.auth.entity; package com.ether.pms.auth.entity;
import jakarta.persistence.*; import jakarta.persistence.*;
import lombok.Data;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.UUID; import java.util.UUID;
import lombok.Data;
/** /**
* 企业用户实体类 * 企业用户实体类
* *
* <p>表示企业类型的用户信息包含员工的职位入职日期等企业相关信息</p> * <p>表示企业类型的用户信息包含员工的职位入职日期等企业相关信息
* *
* <p>与User实体为一对一关系共享主键</p> * <p>与User实体为一对一关系共享主键
* *
* @author Ether开发团队 * @author Ether开发团队
* @version 1.0.0 * @version 1.0.0
@ -21,50 +21,28 @@ import java.util.UUID;
@Data @Data
public class EnterpriseUser { public class EnterpriseUser {
/** /** 用户ID 主键也是外键指向auth_user表 */
* 用户ID @Id private UUID userId;
* 主键也是外键指向auth_user表
*/
@Id
private UUID userId;
/** /** 员工工号 企业内部员工编号 */
* 员工工号
* 企业内部员工编号
*/
@Column(length = 50) @Column(length = 50)
private String employeeNo; private String employeeNo;
/** /** 部门ID 用户所属的部门标识符 */
* 部门ID
* 用户所属的部门标识符
*/
private UUID deptId; private UUID deptId;
/** /** 职位 用户在企业中的职位 */
* 职位
* 用户在企业中的职位
*/
@Column(length = 50) @Column(length = 50)
private String position; private String position;
/** /** 入职日期 员工加入企业的日期 */
* 入职日期
* 员工加入企业的日期
*/
private LocalDate entryDate; private LocalDate entryDate;
/** /** 员工类别 标识员工类型ENTERPRISE普通员工、MANAGEMENT管理层 */
* 员工类别
* 标识员工类型ENTERPRISE普通员工MANAGEMENT管理层
*/
@Column(length = 50) @Column(length = 50)
private String userCategory; private String userCategory;
/** /** 关联的User实体 采用一对一关系,共享主键 */
* 关联的User实体
* 采用一对一关系共享主键
*/
@OneToOne @OneToOne
@MapsId @MapsId
@JoinColumn(name = "user_id") @JoinColumn(name = "user_id")

View File

@ -1,20 +1,19 @@
package com.ether.pms.auth.entity; package com.ether.pms.auth.entity;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern; import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size; import jakarta.validation.constraints.Size;
import lombok.Data;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import lombok.Data;
/** /**
* 权限实体类 * 权限实体类
* *
* <p>用于定义系统中的权限信息包括权限代码名称类型资源路径等属性 * <p>用于定义系统中的权限信息包括权限代码名称类型资源路径等属性 权限是系统安全的基础单元用于控制用户对具体功能的访问能力
* 权限是系统安全的基础单元用于控制用户对具体功能的访问能力</p>
* *
* @author Ether开发团队 * @author Ether开发团队
* @version 1.0.0 * @version 1.0.0
@ -25,79 +24,49 @@ import java.util.UUID;
@Data @Data
public class Permission { public class Permission {
/** /** 权限唯一标识符 使用UUID自动生成 */
* 权限唯一标识符
* 使用UUID自动生成
*/
@Id @Id
@GeneratedValue(strategy = GenerationType.UUID) @GeneratedValue(strategy = GenerationType.UUID)
private UUID id; private UUID id;
/** /** 权限代码 系统内部识别符,必须唯一 格式只能包含字母、数字、冒号和下划线长度2-100位 例如system:user:create, menu:project:view */
* 权限代码
* 系统内部识别符必须唯一
* 格式只能包含字母数字冒号和下划线长度2-100位
* 例如system:user:create, menu:project:view
*/
@NotNull(message = "权限代码不能为空") @NotNull(message = "权限代码不能为空")
@Size(min = 2, max = 100, message = "权限代码长度必须在2-100位之间") @Size(min = 2, max = 100, message = "权限代码长度必须在2-100位之间")
@Pattern(regexp = "^[a-zA-Z0-9_:]+$", message = "权限代码只能包含字母、数字、冒号和下划线") @Pattern(regexp = "^[a-zA-Z0-9_:]+$", message = "权限代码只能包含字母、数字、冒号和下划线")
@Column(unique = true, nullable = false, length = 100) @Column(unique = true, nullable = false, length = 100)
private String code; private String code;
/** /** 权限名称 用于前端展示和用户理解长度2-100位 */
* 权限名称
* 用于前端展示和用户理解长度2-100位
*/
@NotNull(message = "权限名称不能为空") @NotNull(message = "权限名称不能为空")
@Size(min = 2, max = 100, message = "权限名称长度必须在2-100位之间") @Size(min = 2, max = 100, message = "权限名称长度必须在2-100位之间")
@Column(nullable = false, length = 100) @Column(nullable = false, length = 100)
private String name; private String name;
/** /** 权限类型 用于分类权限MENU菜单、BUTTON按钮、API接口等 */
* 权限类型
* 用于分类权限MENU菜单BUTTON按钮API接口
*/
@Size(max = 20, message = "权限类型长度不能超过20位") @Size(max = 20, message = "权限类型长度不能超过20位")
@Column(length = 20) @Column(length = 20)
private String type; private String type;
/** /** 资源路径 对于API类型的权限表示对应的接口路径 */
* 资源路径
* 对于API类型的权限表示对应的接口路径
*/
@Size(max = 50, message = "资源路径长度不能超过50位") @Size(max = 50, message = "资源路径长度不能超过50位")
@Column(length = 50) @Column(length = 50)
private String resource; private String resource;
/** /** 请求方法 对于API类型的权限表示对应的HTTP方法如GET、POST、PUT、DELETE */
* 请求方法
* 对于API类型的权限表示对应的HTTP方法如GETPOSTPUTDELETE
*/
@Size(max = 50, message = "请求方法长度不能超过50位") @Size(max = 50, message = "请求方法长度不能超过50位")
@Column(length = 50) @Column(length = 50)
private String method; private String method;
/** /** 权限描述 对权限的详细说明用于帮助理解权限用途长度不超过200位 */
* 权限描述
* 对权限的详细说明用于帮助理解权限用途长度不超过200位
*/
@Size(max = 200, message = "权限描述长度不能超过200位") @Size(max = 200, message = "权限描述长度不能超过200位")
@Column(length = 200) @Column(length = 200)
private String description; private String description;
/** /** 父权限代码 用于构建权限树形结构,支持层级管理 顶级权限的父代码为空 */
* 父权限代码
* 用于构建权限树形结构支持层级管理
* 顶级权限的父代码为空
*/
@Column(length = 50) @Column(length = 50)
private String parentCode; private String parentCode;
/** /** 排序序号 用于前端展示时的排序,数字越小越靠前 */
* 排序序号
* 用于前端展示时的排序数字越小越靠前
*/
@Size(max = 200, message = "路由路径长度不能超过200位") @Size(max = 200, message = "路由路径长度不能超过200位")
@Column(length = 200) @Column(length = 200)
private String path; private String path;
@ -115,46 +84,29 @@ public class Permission {
@Column(nullable = false) @Column(nullable = false)
private Boolean visible = true; private Boolean visible = true;
/** /** 权限关联的角色列表 多对多关系通过auth_role_permission关联表维护 JsonIgnoreProperties避免循环序列化 */
* 权限关联的角色列表
* 多对多关系通过auth_role_permission关联表维护
* JsonIgnoreProperties避免循环序列化
*/
@JsonIgnoreProperties({"permissions", "roles"}) @JsonIgnoreProperties({"permissions", "roles"})
@ManyToMany(fetch = FetchType.LAZY) @ManyToMany(fetch = FetchType.LAZY)
@JoinTable( @JoinTable(
name = "auth_role_permission", name = "auth_role_permission",
joinColumns = @JoinColumn(name = "permission_id"), joinColumns = @JoinColumn(name = "permission_id"),
inverseJoinColumns = @JoinColumn(name = "role_id") inverseJoinColumns = @JoinColumn(name = "role_id"))
)
private List<Role> roles; private List<Role> roles;
/** /** 权限创建时间 自动设置,记录创建时刻 */
* 权限创建时间
* 自动设置记录创建时刻
*/
private LocalDateTime createdAt; private LocalDateTime createdAt;
/** /** 权限更新时间 自动设置,每次更新时自动修改 */
* 权限更新时间
* 自动设置每次更新时自动修改
*/
private LocalDateTime updatedAt; private LocalDateTime updatedAt;
/** /** 持久化前回调 自动设置创建时间和更新时间 */
* 持久化前回调
* 自动设置创建时间和更新时间
*/
@PrePersist @PrePersist
public void prePersist() { public void prePersist() {
this.createdAt = LocalDateTime.now(); this.createdAt = LocalDateTime.now();
this.updatedAt = LocalDateTime.now(); this.updatedAt = LocalDateTime.now();
} }
/** /** 更新前回调 自动设置更新时间 */
* 更新前回调
* 自动设置更新时间
*/
@PreUpdate @PreUpdate
public void preUpdate() { public void preUpdate() {
this.updatedAt = LocalDateTime.now(); this.updatedAt = LocalDateTime.now();

View File

@ -1,10 +1,5 @@
package com.ether.pms.auth.entity; package com.ether.pms.auth.entity;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import jakarta.persistence.CascadeType; import jakarta.persistence.CascadeType;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.persistence.Entity; import jakarta.persistence.Entity;
@ -16,14 +11,18 @@ import jakarta.persistence.JoinColumn;
import jakarta.persistence.OneToMany; import jakarta.persistence.OneToMany;
import jakarta.persistence.OneToOne; import jakarta.persistence.OneToOne;
import jakarta.persistence.Table; import jakarta.persistence.Table;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import lombok.Data; import lombok.Data;
/** /**
* 项目员工实体类 * 项目员工实体类
* *
* <p>表示项目类型的员工信息包含所属项目员工类型班次等信息</p> * <p>表示项目类型的员工信息包含所属项目员工类型班次等信息
* *
* <p>与User实体为一对一关系共享主键</p> * <p>与User实体为一对一关系共享主键
* *
* @author Ether开发团队 * @author Ether开发团队
* @version 1.0.0 * @version 1.0.0
@ -34,32 +33,21 @@ import lombok.Data;
@Data @Data
public class ProjectStaff { public class ProjectStaff {
/** /** 主键ID */
* 主键ID
*/
@Id @Id
@GeneratedValue(strategy = GenerationType.UUID) @GeneratedValue(strategy = GenerationType.UUID)
@Column(name = "id") @Column(name = "id")
private UUID id; private UUID id;
/** /** 用户ID 外键指向auth_user表 */
* 用户ID
* 外键指向auth_user表
*/
@Column(name = "user_id", insertable = false, updatable = false) @Column(name = "user_id", insertable = false, updatable = false)
private UUID userId; private UUID userId;
/** /** 所属项目ID 员工所属项目的唯一标识符 */
* 所属项目ID
* 员工所属项目的唯一标识符
*/
@Column(name = "project_id") @Column(name = "project_id")
private UUID projectId; private UUID projectId;
/** /** 所属部门ID 员工所属部门的唯一标识符,用于组织架构管理 */
* 所属部门ID
* 员工所属部门的唯一标识符用于组织架构管理
*/
@Column(name = "dept_id") @Column(name = "dept_id")
private UUID deptId; private UUID deptId;
@ -70,48 +58,30 @@ public class ProjectStaff {
@Column(length = 50) @Column(length = 50)
private String staffType; private String staffType;
/** /** 班次类型 标识员工的班次DAY白班、NIGHT夜班、ROTATION轮班 */
* 班次类型
* 标识员工的班次DAY白班NIGHT夜班ROTATION轮班
*/
@Column(length = 20) @Column(length = 20)
private String shiftType; private String shiftType;
/** /** 班组长ID 员工所属班组长的用户ID */
* 班组长ID
* 员工所属班组长的用户ID
*/
private UUID leaderId; private UUID leaderId;
/** /** 在岗状态 标识员工的在岗状态ASSIGNED已分配、ON_LEAVE请假、TRANSFERRED已调离 */
* 在岗状态
* 标识员工的在岗状态ASSIGNED已分配ON_LEAVE请假TRANSFERRED已调离
*/
@Column(length = 20) @Column(length = 20)
private String assignmentStatus; private String assignmentStatus;
/** /** 员工角色列表 */
* 员工角色列表
*/
@OneToMany(mappedBy = "staff", cascade = CascadeType.ALL, orphanRemoval = true) @OneToMany(mappedBy = "staff", cascade = CascadeType.ALL, orphanRemoval = true)
private List<ProjectStaffRole> staffRoles = new ArrayList<>(); private List<ProjectStaffRole> staffRoles = new ArrayList<>();
/** /** 创建时间 */
* 创建时间
*/
@Column(name = "created_at", updatable = false) @Column(name = "created_at", updatable = false)
private LocalDateTime createdAt; private LocalDateTime createdAt;
/** /** 更新时间 */
* 更新时间
*/
@Column(name = "updated_at") @Column(name = "updated_at")
private LocalDateTime updatedAt; private LocalDateTime updatedAt;
/** /** 关联的User实体 采用一对一关系 */
* 关联的User实体
* 采用一对一关系
*/
@OneToOne @OneToOne
@JoinColumn(name = "user_id", foreignKey = @ForeignKey(name = "fk_project_staff_user")) @JoinColumn(name = "user_id", foreignKey = @ForeignKey(name = "fk_project_staff_user"))
private User user; private User user;

View File

@ -1,8 +1,5 @@
package com.ether.pms.auth.entity; package com.ether.pms.auth.entity;
import java.time.LocalDateTime;
import java.util.UUID;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.persistence.Entity; import jakarta.persistence.Entity;
import jakarta.persistence.FetchType; import jakarta.persistence.FetchType;
@ -13,6 +10,8 @@ import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne; import jakarta.persistence.ManyToOne;
import jakarta.persistence.PrePersist; import jakarta.persistence.PrePersist;
import jakarta.persistence.Table; import jakarta.persistence.Table;
import java.time.LocalDateTime;
import java.util.UUID;
import lombok.Data; import lombok.Data;
@Entity @Entity

View File

@ -2,16 +2,16 @@ package com.ether.pms.auth.entity;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.*; import jakarta.persistence.*;
import lombok.Data;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.UUID; import java.util.UUID;
import lombok.Data;
/** /**
* 住户实体类 * 住户实体类
* *
* <p>表示住户类型的用户信息包含身份证认证状态等住户相关信息</p> * <p>表示住户类型的用户信息包含身份证认证状态等住户相关信息
* *
* <p>与User实体为一对一关系共享主键</p> * <p>与User实体为一对一关系共享主键
* *
* @author Ether开发团队 * @author Ether开发团队
* @version 1.0.0 * @version 1.0.0
@ -22,77 +22,47 @@ import java.util.UUID;
@Data @Data
public class Resident { public class Resident {
/** /** 用户ID 主键也是外键指向auth_user表 */
* 用户ID @Id private UUID userId;
* 主键也是外键指向auth_user表
*/
@Id
private UUID userId;
/** /** 身份证号码 住户的身份证号,用于身份验证 */
* 身份证号码
* 住户的身份证号用于身份验证
*/
@JsonIgnore @JsonIgnore
@Column(length = 18) @Column(length = 18)
private String idCard; private String idCard;
/** /** 住户类型 标识住户类型OWNER业主、FAMILY家属、TENANT租户 */
* 住户类型
* 标识住户类型OWNER业主FAMILY家属TENANT租户
*/
@Column(length = 20) @Column(length = 20)
private String residentType; private String residentType;
/** /** 认证状态 标识住户的认证状态UNVERIFIED未认证、PENDING待审核、VERIFIED已认证、REJECTED已拒绝 */
* 认证状态
* 标识住户的认证状态UNVERIFIED未认证PENDING待审核VERIFIED已认证REJECTED已拒绝
*/
@Column(length = 20) @Column(length = 20)
private String verificationStatus; private String verificationStatus;
/** /** 认证时间 记录住户认证通过的时间 */
* 认证时间
* 记录住户认证通过的时间
*/
private LocalDateTime verifiedAt; private LocalDateTime verifiedAt;
/** /** 认证人ID 执行认证操作的管理员用户ID */
* 认证人ID
* 执行认证操作的管理员用户ID
*/
private UUID verifiedBy; private UUID verifiedBy;
/** /** 关联的User实体 采用一对一关系,共享主键 */
* 关联的User实体
* 采用一对一关系共享主键
*/
@OneToOne @OneToOne
@MapsId @MapsId
@JoinColumn(name = "user_id") @JoinColumn(name = "user_id")
private User user; private User user;
/** /** 创建时间 */
* 创建时间
*/
@Column(name = "created_at", nullable = false) @Column(name = "created_at", nullable = false)
private LocalDateTime createdAt; private LocalDateTime createdAt;
/** /** 更新时间 */
* 更新时间
*/
@Column(name = "updated_at", nullable = false) @Column(name = "updated_at", nullable = false)
private LocalDateTime updatedAt; private LocalDateTime updatedAt;
/** /** 创建人ID */
* 创建人ID
*/
@Column(name = "created_by") @Column(name = "created_by")
private UUID createdBy; private UUID createdBy;
/** /** 更新人ID */
* 更新人ID
*/
@Column(name = "updated_by") @Column(name = "updated_by")
private UUID updatedBy; private UUID updatedBy;

View File

@ -1,16 +1,16 @@
package com.ether.pms.auth.entity; package com.ether.pms.auth.entity;
import jakarta.persistence.*; import jakarta.persistence.*;
import lombok.Data;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.UUID; import java.util.UUID;
import lombok.Data;
/** /**
* 住户房屋关联实体类 * 住户房屋关联实体类
* *
* <p>表示住户与房屋之间的关联关系记录住户与房屋的绑定信息</p> * <p>表示住户与房屋之间的关联关系记录住户与房屋的绑定信息
* *
* <p>支持业主家属租户等多种关系类型以及绑定状态管理</p> * <p>支持业主家属租户等多种关系类型以及绑定状态管理
* *
* @author Ether开发团队 * @author Ether开发团队
* @version 1.0.0 * @version 1.0.0
@ -21,51 +21,30 @@ import java.util.UUID;
@Data @Data
public class ResidentSpace { public class ResidentSpace {
/** /** 关联记录唯一标识符 采用UUID策略自动生成 */
* 关联记录唯一标识符
* 采用UUID策略自动生成
*/
@Id @Id
@GeneratedValue(strategy = GenerationType.UUID) @GeneratedValue(strategy = GenerationType.UUID)
private UUID id; private UUID id;
/** /** 用户ID 关联的用户唯一标识符 */
* 用户ID
* 关联的用户唯一标识符
*/
@Column(name = "user_id", nullable = false) @Column(name = "user_id", nullable = false)
private UUID userId; private UUID userId;
/** /** 房屋ID 关联的房屋唯一标识符 */
* 房屋ID
* 关联的房屋唯一标识符
*/
@Column(name = "space_id", nullable = false) @Column(name = "space_id", nullable = false)
private UUID spaceId; private UUID spaceId;
/** /** 关系类型 标识住户与房屋的关系OWNER业主、FAMILY家属、TENANT租户 */
* 关系类型
* 标识住户与房屋的关系OWNER业主FAMILY家属TENANT租户
*/
@Column(length = 20) @Column(length = 20)
private String relationType; private String relationType;
/** /** 绑定状态 标识关联的状态PENDING待生效、ACTIVE生效中、EXPIRED已过期、CANCELLED已取消 */
* 绑定状态
* 标识关联的状态PENDING待生效ACTIVE生效中EXPIRED已过期CANCELLED已取消
*/
@Column(length = 20) @Column(length = 20)
private String bindingStatus; private String bindingStatus;
/** /** 开始日期 关联关系的生效日期 */
* 开始日期
* 关联关系的生效日期
*/
private LocalDate startDate; private LocalDate startDate;
/** /** 结束日期 关联关系的失效日期,永久关联则为空 */
* 结束日期
* 关联关系的失效日期永久关联则为空
*/
private LocalDate endDate; private LocalDate endDate;
} }

View File

@ -4,16 +4,15 @@ import jakarta.persistence.*;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern; import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size; import jakarta.validation.constraints.Size;
import lombok.Data;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import lombok.Data;
/** /**
* 角色实体类 * 角色实体类
* *
* <p>用于定义系统中的角色信息包括角色代码名称描述类型数据范围等属性 * <p>用于定义系统中的角色信息包括角色代码名称描述类型数据范围等属性 角色与权限通过多对多关联一个角色可以拥有多个权限
* 角色与权限通过多对多关联一个角色可以拥有多个权限</p>
* *
* @author Ether开发团队 * @author Ether开发团队
* @version 1.0.0 * @version 1.0.0
@ -24,122 +23,84 @@ import java.util.UUID;
@Data @Data
public class Role { public class Role {
/** /** 角色唯一标识符 使用UUID自动生成 */
* 角色唯一标识符
* 使用UUID自动生成
*/
@Id @Id
@GeneratedValue(strategy = GenerationType.UUID) @GeneratedValue(strategy = GenerationType.UUID)
private UUID id; private UUID id;
/** /** 角色代码 用于系统内部识别,必须唯一 格式只能包含字母、数字和下划线长度2-50位 */
* 角色代码
* 用于系统内部识别必须唯一
* 格式只能包含字母数字和下划线长度2-50位
*/
@NotNull(message = "角色代码不能为空") @NotNull(message = "角色代码不能为空")
@Size(min = 2, max = 50, message = "角色代码长度必须在2-50位之间") @Size(min = 2, max = 50, message = "角色代码长度必须在2-50位之间")
@Pattern(regexp = "^[a-zA-Z0-9_]+$", message = "角色代码只能包含字母、数字和下划线") @Pattern(regexp = "^[a-zA-Z0-9_]+$", message = "角色代码只能包含字母、数字和下划线")
@Column(unique = true, nullable = false, length = 50) @Column(unique = true, nullable = false, length = 50)
private String code; private String code;
/** /** 角色名称 用于前端展示长度2-50位 */
* 角色名称
* 用于前端展示长度2-50位
*/
@NotNull(message = "角色名称不能为空") @NotNull(message = "角色名称不能为空")
@Size(min = 2, max = 50, message = "角色名称长度必须在2-50位之间") @Size(min = 2, max = 50, message = "角色名称长度必须在2-50位之间")
@Column(nullable = false, length = 50) @Column(nullable = false, length = 50)
private String name; private String name;
/** /** 角色描述 对角色的详细说明长度不超过200位 */
* 角色描述
* 对角色的详细说明长度不超过200位
*/
@Size(max = 200, message = "角色描述长度不能超过200位") @Size(max = 200, message = "角色描述长度不能超过200位")
@Column(length = 200) @Column(length = 200)
private String description; private String description;
/** /** 角色类型 区分系统级、项目级、部门级角色 */
* 角色类型
* 区分系统级项目级部门级角色
*/
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Column(length = 20, nullable = false) @Column(length = 20, nullable = false)
private RoleType type; private RoleType type;
/** /** 数据范围 定义角色可访问的数据范围,默认为本人数据 */
* 数据范围
* 定义角色可访问的数据范围默认为本人数据
*/
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Column(length = 20) @Column(length = 20)
private DataScope dataScope = DataScope.SELF; private DataScope dataScope = DataScope.SELF;
/** /** 所属项目ID 用于项目级角色的项目归属 */
* 所属项目ID
* 用于项目级角色的项目归属
*/
@Column(name = "project_id", columnDefinition = "uuid") @Column(name = "project_id", columnDefinition = "uuid")
private UUID projectId; private UUID projectId;
/** /** 角色状态 启用或禁用,默认为启用 */
* 角色状态
* 启用或禁用默认为启用
*/
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Column(length = 20, nullable = false) @Column(length = 20, nullable = false)
private RoleStatus status = RoleStatus.ENABLED; private RoleStatus status = RoleStatus.ENABLED;
/** /** 角色关联的权限列表 多对多关系通过auth_role_permission关联表维护 */
* 角色关联的权限列表
* 多对多关系通过auth_role_permission关联表维护
*/
@ManyToMany(fetch = FetchType.LAZY) @ManyToMany(fetch = FetchType.LAZY)
@JoinTable( @JoinTable(
name = "auth_role_permission", name = "auth_role_permission",
joinColumns = @JoinColumn(name = "role_id", foreignKey = @ForeignKey(name = "fk_auth_role_permission_role")), joinColumns =
inverseJoinColumns = @JoinColumn(name = "permission_id", foreignKey = @ForeignKey(name = "fk_auth_role_permission_permission")) @JoinColumn(
) name = "role_id",
foreignKey = @ForeignKey(name = "fk_auth_role_permission_role")),
inverseJoinColumns =
@JoinColumn(
name = "permission_id",
foreignKey = @ForeignKey(name = "fk_auth_role_permission_permission")))
private List<Permission> permissions; private List<Permission> permissions;
/** /** 角色创建时间 自动设置,记录创建时刻 */
* 角色创建时间
* 自动设置记录创建时刻
*/
@Column(name = "created_at", nullable = false) @Column(name = "created_at", nullable = false)
private LocalDateTime createdAt; private LocalDateTime createdAt;
/** /** 角色更新时间 自动设置,每次更新时自动修改 */
* 角色更新时间
* 自动设置每次更新时自动修改
*/
@Column(name = "updated_at", nullable = false) @Column(name = "updated_at", nullable = false)
private LocalDateTime updatedAt; private LocalDateTime updatedAt;
/** /** 持久化前回调 自动设置创建时间和更新时间 */
* 持久化前回调
* 自动设置创建时间和更新时间
*/
@PrePersist @PrePersist
public void prePersist() { public void prePersist() {
this.createdAt = LocalDateTime.now(); this.createdAt = LocalDateTime.now();
this.updatedAt = LocalDateTime.now(); this.updatedAt = LocalDateTime.now();
} }
/** /** 更新前回调 自动设置更新时间 */
* 更新前回调
* 自动设置更新时间
*/
@PreUpdate @PreUpdate
public void preUpdate() { public void preUpdate() {
this.updatedAt = LocalDateTime.now(); this.updatedAt = LocalDateTime.now();
} }
/** /** 角色类型枚举 定义角色的层级分类 */
* 角色类型枚举
* 定义角色的层级分类
*/
public enum RoleType { public enum RoleType {
/** 系统级角色,可访问所有项目数据 */ /** 系统级角色,可访问所有项目数据 */
SYSTEM("系统级"), SYSTEM("系统级"),
@ -155,10 +116,7 @@ public class Role {
} }
} }
/** /** 数据范围枚举 定义角色可查看的数据范围级别 */
* 数据范围枚举
* 定义角色可查看的数据范围级别
*/
public enum DataScope { public enum DataScope {
/** 全部数据,可查看所有数据 */ /** 全部数据,可查看所有数据 */
ALL("全部"), ALL("全部"),
@ -176,10 +134,7 @@ public class Role {
} }
} }
/** /** 角色状态枚举 定义角色的启用/禁用状态 */
* 角色状态枚举
* 定义角色的启用/禁用状态
*/
public enum RoleStatus { public enum RoleStatus {
/** 角色已启用,可以正常使用 */ /** 角色已启用,可以正常使用 */
ENABLED("启用"), ENABLED("启用"),

View File

@ -1,16 +1,16 @@
package com.ether.pms.auth.entity; package com.ether.pms.auth.entity;
import jakarta.persistence.*; import jakarta.persistence.*;
import lombok.Data;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.UUID; import java.util.UUID;
import lombok.Data;
/** /**
* 房屋空间实体类 * 房屋空间实体类
* *
* <p>表示项目中的房屋或空间信息包含楼栋单元房号等</p> * <p>表示项目中的房屋或空间信息包含楼栋单元房号等
* *
* <p>支持住宅和商业两种类型的空间管理</p> * <p>支持住宅和商业两种类型的空间管理
* *
* @author Ether开发团队 * @author Ether开发团队
* @version 1.0.0 * @version 1.0.0
@ -21,65 +21,38 @@ import java.util.UUID;
@Data @Data
public class Space { public class Space {
/** /** 空间唯一标识符 采用UUID策略自动生成 */
* 空间唯一标识符
* 采用UUID策略自动生成
*/
@Id @Id
@GeneratedValue(strategy = GenerationType.UUID) @GeneratedValue(strategy = GenerationType.UUID)
private UUID id; private UUID id;
/** /** 所属项目ID 空间所属项目的唯一标识符 */
* 所属项目ID
* 空间所属项目的唯一标识符
*/
@Column(name = "project_id") @Column(name = "project_id")
private UUID projectId; private UUID projectId;
/** /** 楼栋 房屋所在的楼栋号或名称 */
* 楼栋
* 房屋所在的楼栋号或名称
*/
@Column(length = 50) @Column(length = 50)
private String building; private String building;
/** /** 单元 房屋所在的单元号 */
* 单元
* 房屋所在的单元号
*/
@Column(length = 50) @Column(length = 50)
private String unit; private String unit;
/** /** 房号 房屋的房间号 */
* 房号
* 房屋的房间号
*/
@Column(length = 50) @Column(length = 50)
private String roomNo; private String roomNo;
/** /** 房屋类型 标识房屋类型RESIDENTIAL住宅、COMMERCIAL商业 */
* 房屋类型
* 标识房屋类型RESIDENTIAL住宅COMMERCIAL商业
*/
@Column(length = 20) @Column(length = 20)
private String spaceType; private String spaceType;
/** /** 楼层 房屋所在的楼层 */
* 楼层
* 房屋所在的楼层
*/
private Integer floor; private Integer floor;
/** /** 建筑面积 房屋的建筑面积,单位平方米 */
* 建筑面积
* 房屋的建筑面积单位平方米
*/
private BigDecimal unitArea; private BigDecimal unitArea;
/** /** 状态 标识房屋的状态:正常、空置、已售等 */
* 状态
* 标识房屋的状态正常空置已售等
*/
@Column(length = 20) @Column(length = 20)
private String status; private String status;
} }

View File

@ -1,19 +1,17 @@
package com.ether.pms.auth.entity; package com.ether.pms.auth.entity;
import jakarta.persistence.*; import jakarta.persistence.*;
import lombok.Data; import java.time.LocalDateTime;
import lombok.NoArgsConstructor; import java.util.UUID;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.CreationTimestamp; import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp; import org.hibernate.annotations.UpdateTimestamp;
import java.time.LocalDateTime;
import java.util.UUID;
/** /**
* 系统配置实体类 * 系统配置实体类 用于存储系统的键值对配置信息如物业企业名称等
* 用于存储系统的键值对配置信息如物业企业名称等
* *
* @author Ether Team * @author Ether Team
* @since 1.0.0 * @since 1.0.0
@ -26,47 +24,29 @@ import java.util.UUID;
@Builder @Builder
public class SysConfig { public class SysConfig {
/** /** 配置项唯一标识 使用 UUID 自动生成 */
* 配置项唯一标识
* 使用 UUID 自动生成
*/
@Id @Id
@GeneratedValue(strategy = GenerationType.UUID) @GeneratedValue(strategy = GenerationType.UUID)
private UUID id; private UUID id;
/** /** 配置键 唯一标识一个配置项property_company_name */
* 配置键
* 唯一标识一个配置项property_company_name
*/
@Column(name = "config_key", nullable = false, unique = true, length = 128) @Column(name = "config_key", nullable = false, unique = true, length = 128)
private String configKey; private String configKey;
/** /** 配置值 使用 TEXT 类型存储,支持长文本 */
* 配置值
* 使用 TEXT 类型存储支持长文本
*/
@Column(name = "config_value", length = 5000) @Column(name = "config_value", length = 5000)
private String configValue; private String configValue;
/** /** 配置描述 用于说明该配置项的用途 */
* 配置描述
* 用于说明该配置项的用途
*/
@Column(name = "description", length = 256) @Column(name = "description", length = 256)
private String description; private String description;
/** /** 创建时间 自动填充,不可更新 */
* 创建时间
* 自动填充不可更新
*/
@CreationTimestamp @CreationTimestamp
@Column(name = "created_at", nullable = false, updatable = false) @Column(name = "created_at", nullable = false, updatable = false)
private LocalDateTime createdAt; private LocalDateTime createdAt;
/** /** 更新时间 自动填充,更新时自动修改 */
* 更新时间
* 自动填充更新时自动修改
*/
@UpdateTimestamp @UpdateTimestamp
@Column(name = "updated_at", nullable = false) @Column(name = "updated_at", nullable = false)
private LocalDateTime updatedAt; private LocalDateTime updatedAt;

View File

@ -6,17 +6,17 @@ import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern; import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size; import jakarta.validation.constraints.Size;
import lombok.Data;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import lombok.Data;
/** /**
* 用户实体类 * 用户实体类
* *
* <p>表示系统中的用户信息包含用户的基本认证信息和个人资料</p> * <p>表示系统中的用户信息包含用户的基本认证信息和个人资料
* *
* <p>使用JPA注解映射到auth_user表支持基本的CRUD操作</p> * <p>使用JPA注解映射到auth_user表支持基本的CRUD操作
* *
* @author Ether开发团队 * @author Ether开发团队
* @version 1.0.0 * @version 1.0.0
@ -27,157 +27,101 @@ import java.util.UUID;
@Data @Data
public class User { public class User {
/** /** 用户唯一标识符 采用UUID策略自动生成 */
* 用户唯一标识符
* 采用UUID策略自动生成
*/
@Id @Id
@GeneratedValue(strategy = GenerationType.UUID) @GeneratedValue(strategy = GenerationType.UUID)
private UUID id; private UUID id;
/** /** 用户名 用于用户登录唯一标识长度3-50位只能包含字母、数字和下划线 */
* 用户名
* 用于用户登录唯一标识长度3-50位只能包含字母数字和下划线
*/
@NotNull(message = "用户名不能为空") @NotNull(message = "用户名不能为空")
@Size(min = 3, max = 50, message = "用户名长度必须在3-50位之间") @Size(min = 3, max = 50, message = "用户名长度必须在3-50位之间")
@Pattern(regexp = "^[a-zA-Z0-9_]+$", message = "用户名只能包含字母、数字和下划线") @Pattern(regexp = "^[a-zA-Z0-9_]+$", message = "用户名只能包含字母、数字和下划线")
@Column(unique = true, nullable = false, length = 50) @Column(unique = true, nullable = false, length = 50)
private String username; private String username;
/** /** 密码(加密存储) 使用BCrypt加密后的密码原文 */
* 密码加密存储
* 使用BCrypt加密后的密码原文
*/
@NotNull(message = "密码不能为空") @NotNull(message = "密码不能为空")
@JsonIgnore @JsonIgnore
@Column(nullable = false, length = 255) @Column(nullable = false, length = 255)
private String password; private String password;
/** /** 密码盐值 用于增强密码加密的安全性 */
* 密码盐值 @JsonIgnore private String salt;
* 用于增强密码加密的安全性
*/
@JsonIgnore
private String salt;
/** /** 真实姓名 用户的真实姓名,用于显示 */
* 真实姓名
* 用户的真实姓名用于显示
*/
@Column(length = 50) @Column(length = 50)
private String realName; private String realName;
/** /** 手机号码 中国大陆手机号格式11位数字 */
* 手机号码
* 中国大陆手机号格式11位数字
*/
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确") @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
@Column(length = 20) @Column(length = 20)
private String phone; private String phone;
/** /** 电子邮箱 用户的邮箱地址,用于通知和验证 */
* 电子邮箱
* 用户的邮箱地址用于通知和验证
*/
@Email(message = "邮箱格式不正确") @Email(message = "邮箱格式不正确")
@Column(length = 100) @Column(length = 100)
private String email; private String email;
/** /** 头像URL 用户头像的存储路径或URL */
* 头像URL
* 用户头像的存储路径或URL
*/
private String avatar; private String avatar;
/** /** 用户状态 标识用户的当前状态:正常、锁定或禁用 */
* 用户状态
* 标识用户的当前状态正常锁定或禁用
*/
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Column(length = 20, nullable = false) @Column(length = 20, nullable = false)
private UserStatus status = UserStatus.ACTIVE; private UserStatus status = UserStatus.ACTIVE;
/** /** 用户类型 标识用户的类型ENTERPRISE企业用户、PROJECT_STAFF项目员工、RESIDENT住户、CUSTOMER客户 */
* 用户类型
* 标识用户的类型ENTERPRISE企业用户PROJECT_STAFF项目员工RESIDENT住户CUSTOMER客户
*/
@Column(length = 20, nullable = false) @Column(length = 20, nullable = false)
private String userType; private String userType;
/** /** 部门ID 用户所属的部门标识符 */
* 部门ID
* 用户所属的部门标识符
*/
private UUID deptId; private UUID deptId;
/** /** 最后登录时间 记录用户最后一次成功登录的时间 */
* 最后登录时间
* 记录用户最后一次成功登录的时间
*/
private LocalDateTime lastLoginTime; private LocalDateTime lastLoginTime;
/** /** 最后登录IP 记录用户最后一次成功登录的IP地址 */
* 最后登录IP
* 记录用户最后一次成功登录的IP地址
*/
private String lastLoginIp; private String lastLoginIp;
/** /** 用户关联的角色列表 采用懒加载方式获取用户的所有角色 */
* 用户关联的角色列表
* 采用懒加载方式获取用户的所有角色
*/
@ManyToMany(fetch = FetchType.LAZY) @ManyToMany(fetch = FetchType.LAZY)
@JoinTable( @JoinTable(
name = "auth_user_role", name = "auth_user_role",
joinColumns = @JoinColumn(name = "user_id", foreignKey = @ForeignKey(name = "fk_auth_user_role_user")), joinColumns =
inverseJoinColumns = @JoinColumn(name = "role_id", foreignKey = @ForeignKey(name = "fk_auth_user_role_role")) @JoinColumn(
) name = "user_id",
foreignKey = @ForeignKey(name = "fk_auth_user_role_user")),
inverseJoinColumns =
@JoinColumn(
name = "role_id",
foreignKey = @ForeignKey(name = "fk_auth_user_role_role")))
private List<Role> roles; private List<Role> roles;
/** /** 创建时间 记录用户账号的创建时间 */
* 创建时间
* 记录用户账号的创建时间
*/
@Column(name = "created_at", nullable = false) @Column(name = "created_at", nullable = false)
private LocalDateTime createdAt; private LocalDateTime createdAt;
/** /** 更新时间 记录用户信息的最后修改时间 */
* 更新时间
* 记录用户信息的最后修改时间
*/
@Column(name = "updated_at", nullable = false) @Column(name = "updated_at", nullable = false)
private LocalDateTime updatedAt; private LocalDateTime updatedAt;
/** /** 创建人ID 记录创建该用户的管理员或系统ID */
* 创建人ID
* 记录创建该用户的管理员或系统ID
*/
private UUID createdBy; private UUID createdBy;
/** /** 持久化前回调 在用户对象首次保存到数据库前自动设置创建时间和更新时间 */
* 持久化前回调
* 在用户对象首次保存到数据库前自动设置创建时间和更新时间
*/
@PrePersist @PrePersist
public void prePersist() { public void prePersist() {
this.createdAt = LocalDateTime.now(); this.createdAt = LocalDateTime.now();
this.updatedAt = LocalDateTime.now(); this.updatedAt = LocalDateTime.now();
} }
/** /** 更新前回调 在用户对象更新前自动设置更新时间 */
* 更新前回调
* 在用户对象更新前自动设置更新时间
*/
@PreUpdate @PreUpdate
public void preUpdate() { public void preUpdate() {
this.updatedAt = LocalDateTime.now(); this.updatedAt = LocalDateTime.now();
} }
/** /** 用户状态枚举 定义用户的三种状态:正常、锁定、禁用 */
* 用户状态枚举
* 定义用户的三种状态正常锁定禁用
*/
public enum UserStatus { public enum UserStatus {
/** 正常状态 - 用户可以正常登录和使用系统 */ /** 正常状态 - 用户可以正常登录和使用系统 */
ACTIVE("正常"), ACTIVE("正常"),

View File

@ -1,16 +1,16 @@
package com.ether.pms.auth.entity; package com.ether.pms.auth.entity;
import jakarta.persistence.*; import jakarta.persistence.*;
import lombok.Data;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.UUID; import java.util.UUID;
import lombok.Data;
/** /**
* 用户项目关联实体类 * 用户项目关联实体类
* *
* <p>表示用户与项目之间的关联关系记录用户在特定项目中的角色和加入时间</p> * <p>表示用户与项目之间的关联关系记录用户在特定项目中的角色和加入时间
* *
* <p>一个用户可以关联多个项目一个项目也可以关联多个用户形成多对多关系</p> * <p>一个用户可以关联多个项目一个项目也可以关联多个用户形成多对多关系
* *
* @author Ether开发团队 * @author Ether开发团队
* @version 1.0.0 * @version 1.0.0
@ -21,46 +21,28 @@ import java.util.UUID;
@Data @Data
public class UserProject { public class UserProject {
/** /** 关联记录唯一标识符 采用UUID策略自动生成 */
* 关联记录唯一标识符
* 采用UUID策略自动生成
*/
@Id @Id
@GeneratedValue(strategy = GenerationType.UUID) @GeneratedValue(strategy = GenerationType.UUID)
private UUID id; private UUID id;
/** /** 用户ID 关联的用户唯一标识符 */
* 用户ID
* 关联的用户唯一标识符
*/
@Column(name = "user_id", nullable = false) @Column(name = "user_id", nullable = false)
private UUID userId; private UUID userId;
/** /** 项目ID 关联的项目唯一标识符 */
* 项目ID
* 关联的项目唯一标识符
*/
@Column(name = "project_id", nullable = false) @Column(name = "project_id", nullable = false)
private UUID projectId; private UUID projectId;
/** /** 在项目中的角色 记录用户在关联项目中的角色,默认值为"member"(成员) */
* 在项目中的角色
* 记录用户在关联项目中的角色默认值为"member"成员
*/
@Column(name = "role_in_project", nullable = false) @Column(name = "role_in_project", nullable = false)
private String roleInProject = "member"; private String roleInProject = "member";
/** /** 加入时间 记录用户加入项目的时间,默认为当前时间 */
* 加入时间
* 记录用户加入项目的时间默认为当前时间
*/
@Column(name = "joined_at", nullable = false) @Column(name = "joined_at", nullable = false)
private LocalDateTime joinedAt = LocalDateTime.now(); private LocalDateTime joinedAt = LocalDateTime.now();
/** /** 持久化前回调 在关联记录首次保存前,如果加入时间为空则设置为当前时间 */
* 持久化前回调
* 在关联记录首次保存前如果加入时间为空则设置为当前时间
*/
@PrePersist @PrePersist
public void prePersist() { public void prePersist() {
if (this.joinedAt == null) { if (this.joinedAt == null) {

View File

@ -1,6 +1,9 @@
package com.ether.pms.auth.repository; package com.ether.pms.auth.repository;
import com.ether.pms.auth.entity.AuditLog; import com.ether.pms.auth.entity.AuditLog;
import java.time.LocalDateTime;
import java.util.List;
import java.util.UUID;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
@ -10,41 +13,28 @@ import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param; import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import java.time.LocalDateTime;
import java.util.List;
import java.util.UUID;
@Repository @Repository
public interface AuditLogRepository extends JpaRepository<AuditLog, UUID>, JpaSpecificationExecutor<AuditLog> { public interface AuditLogRepository
extends JpaRepository<AuditLog, UUID>, JpaSpecificationExecutor<AuditLog> {
/** /** 查询最近30天的审计日志分页 */
* 查询最近30天的审计日志分页
*/
@Query("SELECT a FROM AuditLog a WHERE a.createdAt >= :startTime ORDER BY a.createdAt DESC") @Query("SELECT a FROM AuditLog a WHERE a.createdAt >= :startTime ORDER BY a.createdAt DESC")
Page<AuditLog> findRecentLogs(@Param("startTime") LocalDateTime startTime, Pageable pageable); Page<AuditLog> findRecentLogs(@Param("startTime") LocalDateTime startTime, Pageable pageable);
/** /** 查询最近30天的审计日志不分页 */
* 查询最近30天的审计日志不分页
*/
@Query("SELECT a FROM AuditLog a WHERE a.createdAt >= :startTime ORDER BY a.createdAt DESC") @Query("SELECT a FROM AuditLog a WHERE a.createdAt >= :startTime ORDER BY a.createdAt DESC")
List<AuditLog> findRecentLogs(@Param("startTime") LocalDateTime startTime); List<AuditLog> findRecentLogs(@Param("startTime") LocalDateTime startTime);
/** /** 删除超过指定时间的日志(归档用) */
* 删除超过指定时间的日志归档用
*/
@Modifying @Modifying
@Query("DELETE FROM AuditLog a WHERE a.createdAt < :cutoffTime") @Query("DELETE FROM AuditLog a WHERE a.createdAt < :cutoffTime")
int deleteByCreatedAtBefore(@Param("cutoffTime") LocalDateTime cutoffTime); int deleteByCreatedAtBefore(@Param("cutoffTime") LocalDateTime cutoffTime);
/** /** 查询超过指定时间的日志(归档用) */
* 查询超过指定时间的日志归档用
*/
@Query("SELECT a FROM AuditLog a WHERE a.createdAt < :cutoffTime ORDER BY a.createdAt DESC") @Query("SELECT a FROM AuditLog a WHERE a.createdAt < :cutoffTime ORDER BY a.createdAt DESC")
List<AuditLog> findByCreatedAtBefore(@Param("cutoffTime") LocalDateTime cutoffTime); List<AuditLog> findByCreatedAtBefore(@Param("cutoffTime") LocalDateTime cutoffTime);
/** /** 统计最近30天的日志数量 */
* 统计最近30天的日志数量
*/
@Query("SELECT COUNT(a) FROM AuditLog a WHERE a.createdAt >= :startTime") @Query("SELECT COUNT(a) FROM AuditLog a WHERE a.createdAt >= :startTime")
long countRecentLogs(@Param("startTime") LocalDateTime startTime); long countRecentLogs(@Param("startTime") LocalDateTime startTime);
} }

View File

@ -1,38 +0,0 @@
package com.ether.pms.auth.repository;
import com.ether.pms.auth.entity.DataAccess;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
@Repository
public interface DataAccessRepository extends JpaRepository<DataAccess, UUID> {
List<DataAccess> findByDataTypeAndDataId(String dataType, UUID dataId);
/**
* 根据多条件精确查询数据访问记录
*
* <p>用于 grantAccess 方法中检查是否已存在相同的访问授权记录</p>
*
* @param dataType 数据类型
* @param dataId 数据ID
* @param accessType 访问类型
* @param accessId 访问者ID
* @return 匹配的访问记录如果存在
*/
Optional<DataAccess> findByDataTypeAndDataIdAndAccessTypeAndAccessId(
String dataType, UUID dataId, String accessType, UUID accessId);
@Query("SELECT da FROM DataAccess da WHERE da.accessType = :accessType AND da.accessId = :accessId")
List<DataAccess> findByAccessTypeAndAccessId(@Param("accessType") String accessType, @Param("accessId") UUID accessId);
@Query("SELECT da.dataId FROM DataAccess da WHERE da.accessType = 'user' AND da.accessId = :userId AND da.dataType = :dataType")
List<UUID> findDataIdsByUserAccess(@Param("userId") UUID userId, @Param("dataType") String dataType);
void deleteByDataTypeAndDataId(String dataType, UUID dataId);
}

View File

@ -1,17 +1,16 @@
package com.ether.pms.auth.repository; package com.ether.pms.auth.repository;
import com.ether.pms.auth.entity.Dept; import com.ether.pms.auth.entity.Dept;
import java.util.List;
import java.util.UUID;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query; import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.UUID;
/** /**
* 部门数据访问层接口 * 部门数据访问层接口
* *
* <p>提供部门数据的持久化操作继承Spring Data JPA的JpaRepository接口</p> * <p>提供部门数据的持久化操作继承Spring Data JPA的JpaRepository接口
* *
* @author Ether开发团队 * @author Ether开发团队
* @version 1.0.0 * @version 1.0.0
@ -23,7 +22,7 @@ public interface DeptRepository extends JpaRepository<Dept, UUID> {
/** /**
* 查询顶级部门列表 * 查询顶级部门列表
* *
* <p>查询没有上级部门的部门即树形结构的根节点</p> * <p>查询没有上级部门的部门即树形结构的根节点
* *
* @return 顶级部门列表 * @return 顶级部门列表
*/ */

View File

@ -1,17 +1,16 @@
package com.ether.pms.auth.repository; package com.ether.pms.auth.repository;
import com.ether.pms.auth.entity.EnterpriseUser; import com.ether.pms.auth.entity.EnterpriseUser;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
/** /**
* 企业用户数据访问层接口 * 企业用户数据访问层接口
* *
* <p>提供企业用户数据的持久化操作继承Spring Data JPA的JpaRepository接口</p> * <p>提供企业用户数据的持久化操作继承Spring Data JPA的JpaRepository接口
* *
* @author Ether开发团队 * @author Ether开发团队
* @version 1.0.0 * @version 1.0.0

View File

@ -1,16 +1,15 @@
package com.ether.pms.auth.repository; package com.ether.pms.auth.repository;
import com.ether.pms.auth.entity.Permission; import com.ether.pms.auth.entity.Permission;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
/** /**
* 权限数据访问层接口 * 权限数据访问层接口
* *
* <p>提供权限Permission实体的数据库操作方法继承Spring Data JPA的JpaRepository * <p>提供权限Permission实体的数据库操作方法继承Spring Data JPA的JpaRepository 支持按权限类型父权限代码等条件查询
* 支持按权限类型父权限代码等条件查询</p>
* *
* @author Ether开发团队 * @author Ether开发团队
* @version 1.0.0 * @version 1.0.0
@ -22,7 +21,7 @@ public interface PermissionRepository extends JpaRepository<Permission, UUID> {
/** /**
* 根据权限类型查询权限列表 * 根据权限类型查询权限列表
* *
* <p>按权限类型筛选如MENU菜单BUTTON按钮API接口</p> * <p>按权限类型筛选如MENU菜单BUTTON按钮API接口
* *
* @param type 权限类型 * @param type 权限类型
* @return 该类型的所有权限列表 * @return 该类型的所有权限列表
@ -32,7 +31,7 @@ public interface PermissionRepository extends JpaRepository<Permission, UUID> {
/** /**
* 根据父权限代码查询子权限列表 * 根据父权限代码查询子权限列表
* *
* <p>用于构建权限树形结构查找属于某个父权限的所有子权限</p> * <p>用于构建权限树形结构查找属于某个父权限的所有子权限
* *
* @param parentCode 父权限代码 * @param parentCode 父权限代码
* @return 该父权限下的所有子权限列表 * @return 该父权限下的所有子权限列表
@ -42,7 +41,7 @@ public interface PermissionRepository extends JpaRepository<Permission, UUID> {
/** /**
* 检查权限代码是否存在 * 检查权限代码是否存在
* *
* <p>用于创建权限时的唯一性校验</p> * <p>用于创建权限时的唯一性校验
* *
* @param code 权限代码 * @param code 权限代码
* @return 存在返回true不存在返回false * @return 存在返回true不存在返回false

View File

@ -1,19 +1,17 @@
package com.ether.pms.auth.repository; package com.ether.pms.auth.repository;
import com.ether.pms.auth.entity.ProjectStaff;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query; import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import com.ether.pms.auth.entity.ProjectStaff;
/** /**
* 项目员工数据访问层接口 * 项目员工数据访问层接口
* *
* <p>提供项目员工数据的持久化操作继承Spring Data JPA的JpaRepository接口</p> * <p>提供项目员工数据的持久化操作继承Spring Data JPA的JpaRepository接口
* *
* @author Ether开发团队 * @author Ether开发团队
* @version 1.0.0 * @version 1.0.0
@ -52,11 +50,12 @@ public interface ProjectStaffRepository extends JpaRepository<ProjectStaff, UUID
* @param projectId 项目唯一标识符 * @param projectId 项目唯一标识符
* @return 该项目下的所有员工列表包含角色信息 * @return 该项目下的所有员工列表包含角色信息
*/ */
@Query("SELECT DISTINCT ps FROM ProjectStaff ps " + @Query(
"LEFT JOIN FETCH ps.staffRoles sr " + "SELECT DISTINCT ps FROM ProjectStaff ps "
"LEFT JOIN FETCH sr.role " + + "LEFT JOIN FETCH ps.staffRoles sr "
"LEFT JOIN FETCH ps.user " + + "LEFT JOIN FETCH sr.role "
"WHERE ps.projectId = :projectId") + "LEFT JOIN FETCH ps.user "
+ "WHERE ps.projectId = :projectId")
List<ProjectStaff> findByProjectIdWithRoles(UUID projectId); List<ProjectStaff> findByProjectIdWithRoles(UUID projectId);
/** /**

View File

@ -1,13 +1,11 @@
package com.ether.pms.auth.repository; package com.ether.pms.auth.repository;
import com.ether.pms.auth.entity.ProjectStaffRole;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import com.ether.pms.auth.entity.ProjectStaffRole;
@Repository @Repository
public interface ProjectStaffRoleRepository extends JpaRepository<ProjectStaffRole, UUID> { public interface ProjectStaffRoleRepository extends JpaRepository<ProjectStaffRole, UUID> {

View File

@ -1,17 +1,16 @@
package com.ether.pms.auth.repository; package com.ether.pms.auth.repository;
import com.ether.pms.auth.entity.Resident; import com.ether.pms.auth.entity.Resident;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
/** /**
* 住户数据访问层接口 * 住户数据访问层接口
* *
* <p>提供住户数据的持久化操作继承Spring Data JPA的JpaRepository接口</p> * <p>提供住户数据的持久化操作继承Spring Data JPA的JpaRepository接口
* *
* @author Ether开发团队 * @author Ether开发团队
* @version 1.0.0 * @version 1.0.0

View File

@ -1,17 +1,16 @@
package com.ether.pms.auth.repository; package com.ether.pms.auth.repository;
import com.ether.pms.auth.entity.ResidentSpace; import com.ether.pms.auth.entity.ResidentSpace;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
/** /**
* 住户房屋关联数据访问层接口 * 住户房屋关联数据访问层接口
* *
* <p>提供住户房屋关联数据的持久化操作继承Spring Data JPA的JpaRepository接口</p> * <p>提供住户房屋关联数据的持久化操作继承Spring Data JPA的JpaRepository接口
* *
* @author Ether开发团队 * @author Ether开发团队
* @version 1.0.0 * @version 1.0.0

View File

@ -1,18 +1,17 @@
package com.ether.pms.auth.repository; package com.ether.pms.auth.repository;
import com.ether.pms.auth.entity.Role; import com.ether.pms.auth.entity.Role;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
/** /**
* 角色数据访问层接口 * 角色数据访问层接口
* *
* <p>提供角色Role实体的数据库操作方法继承Spring Data JPA的JpaRepository * <p>提供角色Role实体的数据库操作方法继承Spring Data JPA的JpaRepository 支持按角色代码项目ID类型等条件查询以及带权限信息的复杂查询
* 支持按角色代码项目ID类型等条件查询以及带权限信息的复杂查询</p>
* *
* @author Ether开发团队 * @author Ether开发团队
* @version 1.0.0 * @version 1.0.0
@ -24,7 +23,7 @@ public interface RoleRepository extends JpaRepository<Role, UUID> {
/** /**
* 根据角色代码查询角色 * 根据角色代码查询角色
* *
* <p>角色代码在系统中唯一用于精确查找特定角色</p> * <p>角色代码在系统中唯一用于精确查找特定角色
* *
* @param code 角色代码 * @param code 角色代码
* @return 角色对象如果存在 * @return 角色对象如果存在
@ -34,7 +33,7 @@ public interface RoleRepository extends JpaRepository<Role, UUID> {
/** /**
* 检查角色代码是否存在 * 检查角色代码是否存在
* *
* <p>用于创建角色时的唯一性校验</p> * <p>用于创建角色时的唯一性校验
* *
* @param code 角色代码 * @param code 角色代码
* @return 存在返回true不存在返回false * @return 存在返回true不存在返回false
@ -44,7 +43,7 @@ public interface RoleRepository extends JpaRepository<Role, UUID> {
/** /**
* 根据项目ID查询角色列表 * 根据项目ID查询角色列表
* *
* <p>获取指定项目下的所有角色通常用于项目级别的角色筛选</p> * <p>获取指定项目下的所有角色通常用于项目级别的角色筛选
* *
* @param projectId 项目ID * @param projectId 项目ID
* @return 该项目下的角色列表 * @return 该项目下的角色列表
@ -54,7 +53,7 @@ public interface RoleRepository extends JpaRepository<Role, UUID> {
/** /**
* 根据角色类型查询角色列表 * 根据角色类型查询角色列表
* *
* <p>按角色类型如系统级项目级部门级筛选角色</p> * <p>按角色类型如系统级项目级部门级筛选角色
* *
* @param type 角色类型枚举 * @param type 角色类型枚举
* @return 该类型的所有角色列表 * @return 该类型的所有角色列表
@ -64,8 +63,7 @@ public interface RoleRepository extends JpaRepository<Role, UUID> {
/** /**
* 根据角色ID查询角色及其关联的权限信息 * 根据角色ID查询角色及其关联的权限信息
* *
* <p>使用EntityGraph eagerly加载permissions关联数据 * <p>使用EntityGraph eagerly加载permissions关联数据 避免N+1查询问题提升查询性能
* 避免N+1查询问题提升查询性能</p>
* *
* @param id 角色ID * @param id 角色ID
* @return 包含权限信息的角色对象如果存在 * @return 包含权限信息的角色对象如果存在

View File

@ -1,17 +1,16 @@
package com.ether.pms.auth.repository; package com.ether.pms.auth.repository;
import com.ether.pms.auth.entity.Space; import com.ether.pms.auth.entity.Space;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
/** /**
* 房屋空间数据访问层接口 * 房屋空间数据访问层接口
* *
* <p>提供房屋空间数据的持久化操作继承Spring Data JPA的JpaRepository接口</p> * <p>提供房屋空间数据的持久化操作继承Spring Data JPA的JpaRepository接口
* *
* @author Ether开发团队 * @author Ether开发团队
* @version 1.0.0 * @version 1.0.0
@ -37,5 +36,6 @@ public interface SpaceRepository extends JpaRepository<Space, UUID> {
* @param roomNo 房号 * @param roomNo 房号
* @return 包含房屋空间的Optional对象 * @return 包含房屋空间的Optional对象
*/ */
Optional<Space> findByProjectIdAndBuildingAndUnitAndRoomNo(UUID projectId, String building, String unit, String roomNo); Optional<Space> findByProjectIdAndBuildingAndUnitAndRoomNo(
UUID projectId, String building, String unit, String roomNo);
} }

View File

@ -1,15 +1,13 @@
package com.ether.pms.auth.repository; package com.ether.pms.auth.repository;
import com.ether.pms.auth.entity.SysConfig; import com.ether.pms.auth.entity.SysConfig;
import java.util.Optional;
import java.util.UUID;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import java.util.Optional;
import java.util.UUID;
/** /**
* 系统配置数据访问层 * 系统配置数据访问层 提供对 sys_config 表的 CRUD 操作
* 提供对 sys_config 表的 CRUD 操作
* *
* @author Ether Team * @author Ether Team
* @since 1.0.0 * @since 1.0.0

View File

@ -1,21 +1,21 @@
package com.ether.pms.auth.repository; package com.ether.pms.auth.repository;
import com.ether.pms.auth.entity.UserProject; import com.ether.pms.auth.entity.UserProject;
import java.util.List;
import java.util.UUID;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query; import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param; import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.UUID;
/** /**
* 用户项目关联数据访问层接口 * 用户项目关联数据访问层接口
* *
* <p>提供用户与项目关联关系的持久化操作继承Spring Data JPA的JpaRepository接口</p> * <p>提供用户与项目关联关系的持久化操作继承Spring Data JPA的JpaRepository接口
* *
* <p>支持用户项目关系查询分页批量操作等功能</p> * <p>支持用户项目关系查询分页批量操作等功能
* *
* @author Ether开发团队 * @author Ether开发团队
* @version 1.0.0 * @version 1.0.0
@ -27,7 +27,7 @@ public interface UserProjectRepository extends JpaRepository<UserProject, UUID>
/** /**
* 根据用户ID查询该用户关联的所有项目 * 根据用户ID查询该用户关联的所有项目
* *
* <p>返回指定用户参与的所有项目关联记录列表</p> * <p>返回指定用户参与的所有项目关联记录列表
* *
* @param userId 用户唯一标识符 * @param userId 用户唯一标识符
* @return 该用户关联的所有项目列表 * @return 该用户关联的所有项目列表
@ -37,7 +37,7 @@ public interface UserProjectRepository extends JpaRepository<UserProject, UUID>
/** /**
* 根据项目ID查询该项目的所有关联用户 * 根据项目ID查询该项目的所有关联用户
* *
* <p>返回参与指定项目的所有用户关联记录列表</p> * <p>返回参与指定项目的所有用户关联记录列表
* *
* @param projectId 项目唯一标识符 * @param projectId 项目唯一标识符
* @return 该项目关联的所有用户列表 * @return 该项目关联的所有用户列表
@ -47,7 +47,7 @@ public interface UserProjectRepository extends JpaRepository<UserProject, UUID>
/** /**
* 根据项目ID分页查询该项目的所有关联用户 * 根据项目ID分页查询该项目的所有关联用户
* *
* <p>支持分页展示项目成员列表</p> * <p>支持分页展示项目成员列表
* *
* @param projectId 项目唯一标识符 * @param projectId 项目唯一标识符
* @param pageable 分页参数包含页码和每页大小 * @param pageable 分页参数包含页码和每页大小
@ -58,7 +58,7 @@ public interface UserProjectRepository extends JpaRepository<UserProject, UUID>
/** /**
* 根据用户ID查询该用户关联的所有项目ID * 根据用户ID查询该用户关联的所有项目ID
* *
* <p>仅返回项目ID列表用于快速判断用户参与的项目</p> * <p>仅返回项目ID列表用于快速判断用户参与的项目
* *
* @param userId 用户唯一标识符 * @param userId 用户唯一标识符
* @return 该用户关联的所有项目ID列表 * @return 该用户关联的所有项目ID列表
@ -69,7 +69,7 @@ public interface UserProjectRepository extends JpaRepository<UserProject, UUID>
/** /**
* 检查用户与项目的关联是否存在 * 检查用户与项目的关联是否存在
* *
* <p>用于判断用户是否参与了指定项目</p> * <p>用于判断用户是否参与了指定项目
* *
* @param userId 用户唯一标识符 * @param userId 用户唯一标识符
* @param projectId 项目唯一标识符 * @param projectId 项目唯一标识符
@ -80,15 +80,13 @@ public interface UserProjectRepository extends JpaRepository<UserProject, UUID>
/** /**
* 删除用户与项目的关联 * 删除用户与项目的关联
* *
* <p>根据用户ID和项目ID删除关联记录用于用户退出项目或移除项目成员</p> * <p>根据用户ID和项目ID删除关联记录用于用户退出项目或移除项目成员
* *
* @param userId 用户唯一标识符 * @param userId 用户唯一标识符
* @param projectId 项目唯一标识符 * @param projectId 项目唯一标识符
*/ */
void deleteByUserIdAndProjectId(UUID userId, UUID projectId); void deleteByUserIdAndProjectId(UUID userId, UUID projectId);
/** /** 统计项目下的成员数量 */
* 统计项目下的成员数量
*/
long countByProjectId(UUID projectId); long countByProjectId(UUID projectId);
} }

View File

@ -1,22 +1,22 @@
package com.ether.pms.auth.repository; package com.ether.pms.auth.repository;
import com.ether.pms.auth.entity.User; import com.ether.pms.auth.entity.User;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query; import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param; import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
/** /**
* 用户数据访问层接口 * 用户数据访问层接口
* *
* <p>提供用户数据的持久化操作继承Spring Data JPA的JpaRepository接口</p> * <p>提供用户数据的持久化操作继承Spring Data JPA的JpaRepository接口
* *
* <p>包含用户查询角色关联查询等数据库操作方法</p> * <p>包含用户查询角色关联查询等数据库操作方法
* *
* @author Ether开发团队 * @author Ether开发团队
* @version 1.0.0 * @version 1.0.0
@ -28,7 +28,7 @@ public interface UserRepository extends JpaRepository<User, UUID> {
/** /**
* 根据用户名查询用户 * 根据用户名查询用户
* *
* <p>最基础的按用户名查询方法返回Optional包装的用户对象</p> * <p>最基础的按用户名查询方法返回Optional包装的用户对象
* *
* @param username 用户名 * @param username 用户名
* @return 包含用户的Optional对象如果不存在则为空 * @return 包含用户的Optional对象如果不存在则为空
@ -38,7 +38,7 @@ public interface UserRepository extends JpaRepository<User, UUID> {
/** /**
* 根据用户名查询用户及其关联的角色 * 根据用户名查询用户及其关联的角色
* *
* <p>使用LEFT JOIN FETCH预加载用户的角色信息避免N+1查询问题</p> * <p>使用LEFT JOIN FETCH预加载用户的角色信息避免N+1查询问题
* *
* @param username 用户名 * @param username 用户名
* @return 包含用户及其角色的Optional对象 * @return 包含用户及其角色的Optional对象
@ -49,8 +49,9 @@ public interface UserRepository extends JpaRepository<User, UUID> {
/** /**
* 查询所有用户及其关联的角色 * 查询所有用户及其关联的角色
* *
* <p>一次性加载所有用户及其角色信息适用于管理后台用户列表</p> * <p>一次性加载所有用户及其角色信息适用于管理后台用户列表
* <p><b>警告</b>此方法会加载全表数据建议使用分页版本</p> *
* <p><b>警告</b>此方法会加载全表数据建议使用分页版本
* *
* @return 包含所有用户及其角色的列表 * @return 包含所有用户及其角色的列表
* @deprecated 建议使用 {@link #findAllWithRoles(Pageable)} 分页版本 * @deprecated 建议使用 {@link #findAllWithRoles(Pageable)} 分页版本
@ -62,7 +63,7 @@ public interface UserRepository extends JpaRepository<User, UUID> {
/** /**
* 分页查询所有用户及其关联的角色 * 分页查询所有用户及其关联的角色
* *
* <p>支持分页加载用户及其角色信息避免内存溢出风险</p> * <p>支持分页加载用户及其角色信息避免内存溢出风险
* *
* @param pageable 分页参数页码每页大小排序等 * @param pageable 分页参数页码每页大小排序等
* @return 包含用户及其角色的分页数据 * @return 包含用户及其角色的分页数据
@ -73,7 +74,7 @@ public interface UserRepository extends JpaRepository<User, UUID> {
/** /**
* 根据ID查询用户及其关联的角色 * 根据ID查询用户及其关联的角色
* *
* <p>使用LEFT JOIN FETCH预加载用户的角色信息</p> * <p>使用LEFT JOIN FETCH预加载用户的角色信息
* *
* @param id 用户唯一标识符 * @param id 用户唯一标识符
* @return 包含用户及其角色的Optional对象 * @return 包含用户及其角色的Optional对象
@ -84,7 +85,7 @@ public interface UserRepository extends JpaRepository<User, UUID> {
/** /**
* 根据角色ID查询所有拥有该角色的用户 * 根据角色ID查询所有拥有该角色的用户
* *
* <p>用于查询具有特定角色的所有用户列表</p> * <p>用于查询具有特定角色的所有用户列表
* *
* @param roleId 角色唯一标识符 * @param roleId 角色唯一标识符
* @return 拥有该角色的所有用户列表 * @return 拥有该角色的所有用户列表
@ -95,7 +96,7 @@ public interface UserRepository extends JpaRepository<User, UUID> {
/** /**
* 检查用户名是否存在 * 检查用户名是否存在
* *
* <p>用于用户注册时验证用户名唯一性</p> * <p>用于用户注册时验证用户名唯一性
* *
* @param username 用户名 * @param username 用户名
* @return 存在返回true不存在返回false * @return 存在返回true不存在返回false
@ -105,7 +106,7 @@ public interface UserRepository extends JpaRepository<User, UUID> {
/** /**
* 检查手机号是否存在 * 检查手机号是否存在
* *
* <p>用于用户注册或更新时验证手机号唯一性</p> * <p>用于用户注册或更新时验证手机号唯一性
* *
* @param phone 手机号码 * @param phone 手机号码
* @return 存在返回true不存在返回false * @return 存在返回true不存在返回false
@ -115,7 +116,7 @@ public interface UserRepository extends JpaRepository<User, UUID> {
/** /**
* 根据用户类型查询用户列表 * 根据用户类型查询用户列表
* *
* <p>用于查询特定类型的用户如ENTERPRISEPROJECT_STAFF等</p> * <p>用于查询特定类型的用户如ENTERPRISEPROJECT_STAFF等
* *
* @param userType 用户类型 * @param userType 用户类型
* @return 该类型的所有用户列表 * @return 该类型的所有用户列表
@ -125,7 +126,7 @@ public interface UserRepository extends JpaRepository<User, UUID> {
/** /**
* 根据部门ID查询用户列表 * 根据部门ID查询用户列表
* *
* <p>用于查询属于特定部门的所有用户</p> * <p>用于查询属于特定部门的所有用户
* *
* @param deptId 部门唯一标识符 * @param deptId 部门唯一标识符
* @return 该部门下的所有用户列表 * @return 该部门下的所有用户列表
@ -135,11 +136,12 @@ public interface UserRepository extends JpaRepository<User, UUID> {
/** /**
* 根据项目ID查询项目员工 * 根据项目ID查询项目员工
* *
* <p>通过关联ProjectStaff表查询属于指定项目的所有员工用户</p> * <p>通过关联ProjectStaff表查询属于指定项目的所有员工用户
* *
* @param projectId 项目唯一标识符 * @param projectId 项目唯一标识符
* @return该项目下的所有员工用户列表 * @return该项目下的所有员工用户列表
*/ */
@Query("SELECT u FROM User u JOIN ProjectStaff ps ON u.id = ps.userId WHERE ps.projectId = :projectId") @Query(
"SELECT u FROM User u JOIN ProjectStaff ps ON u.id = ps.userId WHERE ps.projectId = :projectId")
List<User> findProjectStaffsByProjectId(@Param("projectId") UUID projectId); List<User> findProjectStaffsByProjectId(@Param("projectId") UUID projectId);
} }

View File

@ -13,10 +13,7 @@ public class AuditLogArchiveTask {
private final AuditLogService auditLogService; private final AuditLogService auditLogService;
/** /** 每天凌晨2点执行归档任务 将超过90天的审计日志归档当前实现为删除实际应导出到文件/对象存储) */
* 每天凌晨2点执行归档任务
* 将超过90天的审计日志归档当前实现为删除实际应导出到文件/对象存储
*/
@Scheduled(cron = "0 0 2 * * ?") @Scheduled(cron = "0 0 2 * * ?")
public void archiveOldAuditLogs() { public void archiveOldAuditLogs() {
log.info("开始执行审计日志归档任务..."); log.info("开始执行审计日志归档任务...");

View File

@ -1,138 +1,27 @@
package com.ether.pms.auth.service; package com.ether.pms.auth.service;
import com.ether.pms.auth.entity.AuditLog; import com.ether.pms.auth.entity.AuditLog;
import com.ether.pms.auth.repository.AuditLogRepository; import java.time.LocalDateTime;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.persistence.criteria.Predicate;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime; public interface AuditLogService {
import java.util.ArrayList;
import java.util.List;
@Service void saveAuditLogAsync(AuditLog auditLog);
@RequiredArgsConstructor
@Slf4j
public class AuditLogService {
private final AuditLogRepository auditLogRepository; AuditLog saveAuditLog(AuditLog auditLog);
private final ObjectMapper objectMapper;
/** Page<AuditLog> findRecentLogs(Pageable pageable);
* 异步保存审计日志
*/
@Async("auditLogExecutor")
public void saveAuditLogAsync(AuditLog auditLog) {
try {
auditLogRepository.save(auditLog);
} catch (Exception e) {
log.error("保存审计日志失败: {}", e.getMessage(), e);
}
}
/** Page<AuditLog> searchLogs(
* 同步保存审计日志用于需要立即确认的场景 String module,
*/ String action,
@Transactional String username,
public AuditLog saveAuditLog(AuditLog auditLog) { LocalDateTime startDate,
return auditLogRepository.save(auditLog); LocalDateTime endDate,
} Pageable pageable);
/** int archiveOldLogs();
* 分页查询审计日志最近30天
*/
public Page<AuditLog> findRecentLogs(Pageable pageable) {
LocalDateTime startTime = LocalDateTime.now().minusDays(30);
return auditLogRepository.findRecentLogs(startTime, pageable);
}
/** long getRecentLogCount();
* 条件查询审计日志最近30天内
*/
public Page<AuditLog> searchLogs(String module, String action, String username,
LocalDateTime startDate, LocalDateTime endDate,
Pageable pageable) {
// 确保查询范围不超过30天
LocalDateTime maxStartDate = LocalDateTime.now().minusDays(30);
if (startDate == null || startDate.isBefore(maxStartDate)) {
startDate = maxStartDate;
}
if (endDate == null) {
endDate = LocalDateTime.now();
}
final LocalDateTime finalStartDate = startDate;
final LocalDateTime finalEndDate = endDate;
Specification<AuditLog> spec = (root, query, cb) -> {
List<Predicate> predicates = new ArrayList<>();
// 时间范围
predicates.add(cb.greaterThanOrEqualTo(root.get("createdAt"), finalStartDate));
predicates.add(cb.lessThanOrEqualTo(root.get("createdAt"), finalEndDate));
// 模块筛选
if (module != null && !module.isEmpty()) {
predicates.add(cb.equal(root.get("module"), module));
}
// 操作类型筛选
if (action != null && !action.isEmpty()) {
predicates.add(cb.equal(root.get("action"), AuditLog.ActionType.valueOf(action)));
}
// 用户名筛选
if (username != null && !username.isEmpty()) {
predicates.add(cb.like(root.get("username"), "%" + username + "%"));
}
return cb.and(predicates.toArray(new Predicate[0]));
};
return auditLogRepository.findAll(spec, pageable);
}
/**
* 归档超过90天的日志
*/
@Transactional
public int archiveOldLogs() {
LocalDateTime cutoffTime = LocalDateTime.now().minusDays(90);
// 查询需要归档的日志
List<AuditLog> oldLogs = auditLogRepository.findByCreatedAtBefore(cutoffTime);
if (oldLogs.isEmpty()) {
log.info("没有需要归档的审计日志");
return 0;
}
// TODO: 将日志导出到文件或对象存储
// 这里简化处理直接删除
// 实际项目中应该
// 1. 将数据导出为JSON/Parquet文件
// 2. 上传到对象存储MinIO/S3
// 3. 记录归档信息
// 4. 然后删除数据库记录
int deleted = auditLogRepository.deleteByCreatedAtBefore(cutoffTime);
log.info("归档审计日志完成,共归档 {} 条记录", deleted);
return deleted;
}
/**
* 获取最近30天的日志统计
*/
public long getRecentLogCount() {
LocalDateTime startTime = LocalDateTime.now().minusDays(30);
return auditLogRepository.countRecentLogs(startTime);
}
} }

View File

@ -1,51 +0,0 @@
package com.ether.pms.auth.service;
import com.ether.pms.auth.entity.DataAccess;
import com.ether.pms.auth.repository.DataAccessRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
@Service
@RequiredArgsConstructor
public class DataAccessService {
private final DataAccessRepository dataAccessRepository;
@Transactional
public DataAccess grantAccess(String dataType, UUID dataId, String accessType, UUID accessId, String level, UUID grantedBy) {
// 使用精确查询替代全表扫描避免 OOM 风险
Optional<DataAccess> existing = dataAccessRepository
.findByDataTypeAndDataIdAndAccessTypeAndAccessId(dataType, dataId, accessType, accessId);
if (existing.isPresent()) {
DataAccess existingAccess = existing.get();
existingAccess.setAccessLevel(level);
existingAccess.setGrantedBy(grantedBy);
return dataAccessRepository.save(existingAccess);
}
DataAccess access = new DataAccess();
access.setDataType(dataType);
access.setDataId(dataId);
access.setAccessType(accessType);
access.setAccessId(accessId);
access.setAccessLevel(level);
access.setGrantedBy(grantedBy);
return dataAccessRepository.save(access);
}
@Transactional
public void revokeAccess(UUID accessId) {
dataAccessRepository.deleteById(accessId);
}
public List<DataAccess> getDataAccess(String dataType, UUID dataId) {
return dataAccessRepository.findByDataTypeAndDataId(dataType, dataId);
}
public List<UUID> getAccessibleDataIds(String dataType, UUID userId) {
return dataAccessRepository.findDataIdsByUserAccess(userId, dataType);
}
}

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