diff --git a/uniro_backend/build.gradle b/uniro_backend/build.gradle index f5f78cc..ea48f8e 100644 --- a/uniro_backend/build.gradle +++ b/uniro_backend/build.gradle @@ -66,6 +66,9 @@ dependencies { //webClient implementation 'org.springframework.boot:spring-boot-starter-webflux' + //hibernate envers + implementation 'org.hibernate.orm:hibernate-envers:6.3.1.Final' + // actuator implementation 'org.springframework.boot:spring-boot-starter-actuator' } diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/annotation/RevisionOperation.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/annotation/RevisionOperation.java new file mode 100644 index 0000000..a767ece --- /dev/null +++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/annotation/RevisionOperation.java @@ -0,0 +1,12 @@ +package com.softeer5.uniro_backend.admin.annotation; + +import com.softeer5.uniro_backend.admin.entity.RevisionOperationType; + +import java.lang.annotation.*; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface RevisionOperation { + RevisionOperationType value() default RevisionOperationType.DEFAULT; +} diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/aspect/RevisionOperationAspect.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/aspect/RevisionOperationAspect.java new file mode 100644 index 0000000..8cab26d --- /dev/null +++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/aspect/RevisionOperationAspect.java @@ -0,0 +1,68 @@ +package com.softeer5.uniro_backend.admin.aspect; + +import com.softeer5.uniro_backend.admin.annotation.RevisionOperation; +import com.softeer5.uniro_backend.admin.entity.RevisionOperationType; +import com.softeer5.uniro_backend.admin.setting.RevisionContext; +import com.softeer5.uniro_backend.route.dto.PostRiskReqDTO; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import static com.softeer5.uniro_backend.common.constant.UniroConst.*; + +@Aspect +@Component +@Order(BEFORE_DEFAULT_ORDER) +public class RevisionOperationAspect { + + @Around("@annotation(revisionOperation)") + public Object around(ProceedingJoinPoint joinPoint, RevisionOperation revisionOperation) throws Throwable { + RevisionOperationType opType = revisionOperation.value(); + + Object result; + switch (opType) { + case UPDATE_RISK -> result = updateRiskHandler(joinPoint); + default -> result = joinPoint.proceed(); + } + + return result; + } + + private Object updateRiskHandler(ProceedingJoinPoint joinPoint) throws Throwable { + MethodSignature signature = (MethodSignature) joinPoint.getSignature(); + String[] parameterNames = signature.getParameterNames(); + Object[] args = joinPoint.getArgs(); + + Long univId = null; + String action = null; + for (int i = 0; i < args.length; i++) { + if (args[i] instanceof Long && "univId".equals(parameterNames[i])) { + univId = (Long) args[i]; + } + else if(args[i] instanceof PostRiskReqDTO postRiskReqDTO){ + int cautionSize = postRiskReqDTO.getCautionTypes().size(); + int dangerSize = postRiskReqDTO.getDangerTypes().size(); + + if (cautionSize > 0) { + action = "주의요소 업데이트"; + } else if (dangerSize > 0) { + action = "위험요소 업데이트"; + } else { + action = "위험/주의요소 해제"; + } + } + } + RevisionContext.setUnivId(univId); + RevisionContext.setAction(action); + try{ + return joinPoint.proceed(); + } + finally { + RevisionContext.clear(); + } + } + +} diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/controller/AdminApi.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/controller/AdminApi.java new file mode 100644 index 0000000..e28d4d1 --- /dev/null +++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/controller/AdminApi.java @@ -0,0 +1,24 @@ +package com.softeer5.uniro_backend.admin.controller; + +import com.softeer5.uniro_backend.admin.dto.RevInfoDTO; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; + +import java.util.List; + +@Tag(name = "admin 페이지 API") +public interface AdminApi { + + @Operation(summary = "모든 버전정보 조회") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "모든 버전정보 조회 성공"), + @ApiResponse(responseCode = "400", description = "EXCEPTION(임시)", content = @Content), + }) + ResponseEntity> getAllRev(@PathVariable("univId") Long univId); + +} diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/controller/AdminController.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/controller/AdminController.java new file mode 100644 index 0000000..1ed101a --- /dev/null +++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/controller/AdminController.java @@ -0,0 +1,23 @@ +package com.softeer5.uniro_backend.admin.controller; + +import com.softeer5.uniro_backend.admin.dto.RevInfoDTO; +import com.softeer5.uniro_backend.admin.service.AdminService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +@RequiredArgsConstructor +public class AdminController implements AdminApi { + private final AdminService adminService; + + @Override + @GetMapping("/admin/revision/{univId}") + public ResponseEntity> getAllRev(@PathVariable("univId") Long univId) { + return ResponseEntity.ok().body(adminService.getAllRevInfo(univId)); + } +} diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/dto/RevInfoDTO.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/dto/RevInfoDTO.java new file mode 100644 index 0000000..398f4da --- /dev/null +++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/dto/RevInfoDTO.java @@ -0,0 +1,25 @@ +package com.softeer5.uniro_backend.admin.dto; + +import com.softeer5.uniro_backend.admin.entity.RevInfo; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; + +import java.time.LocalDateTime; + +@Getter +@Schema(name = "GetBuildingResDTO", description = "건물 노드 조회 DTO") +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +public class RevInfoDTO { + @Schema(description = "버전명", example = "4") + private final Long rev; // Revision 번호 + @Schema(description = "버전 타임스탬프", example = "2025-02-04T17:56:06.832") + private final LocalDateTime revTime; // Revision 시간 + @Schema(description = "학교id", example = "1") + private final Long univId; //UnivId + @Schema(description = "변경사항 desc", example = "위험요소 추가") + private final String action; // 행위 + + public static RevInfoDTO of(Long rev, LocalDateTime revTime, Long univId, String action) { + return new RevInfoDTO(rev, revTime, univId, action); + } +} \ No newline at end of file diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/entity/RevInfo.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/entity/RevInfo.java new file mode 100644 index 0000000..36a6774 --- /dev/null +++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/entity/RevInfo.java @@ -0,0 +1,28 @@ +package com.softeer5.uniro_backend.admin.entity; + +import com.softeer5.uniro_backend.admin.setting.CustomReversionListener; +import jakarta.persistence.*; +import lombok.Getter; +import lombok.Setter; +import org.hibernate.envers.RevisionEntity; +import org.hibernate.envers.RevisionNumber; +import org.hibernate.envers.RevisionTimestamp; + +@Entity +@RevisionEntity(CustomReversionListener.class) +@Getter +@Setter +@Table(name = "Revinfo") +public class RevInfo { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @RevisionNumber + private Long rev; + + @RevisionTimestamp + @Column(name = "revtstmp") + private long revTimeStamp; + @Column(name = "univ_id", nullable = false) + private Long univId; + private String action; +} diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/entity/RevisionOperationType.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/entity/RevisionOperationType.java new file mode 100644 index 0000000..10b9ee7 --- /dev/null +++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/entity/RevisionOperationType.java @@ -0,0 +1,6 @@ +package com.softeer5.uniro_backend.admin.entity; + +public enum RevisionOperationType { + UPDATE_RISK, + DEFAULT; +} diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/repository/RevInfoRepository.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/repository/RevInfoRepository.java new file mode 100644 index 0000000..a7ab85c --- /dev/null +++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/repository/RevInfoRepository.java @@ -0,0 +1,11 @@ +package com.softeer5.uniro_backend.admin.repository; + +import com.softeer5.uniro_backend.admin.entity.RevInfo; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + + +public interface RevInfoRepository extends JpaRepository { + List findAllByUnivId(Long univId); +} diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/service/AdminService.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/service/AdminService.java new file mode 100644 index 0000000..49a256d --- /dev/null +++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/service/AdminService.java @@ -0,0 +1,26 @@ +package com.softeer5.uniro_backend.admin.service; + +import com.softeer5.uniro_backend.admin.dto.RevInfoDTO; +import com.softeer5.uniro_backend.admin.repository.RevInfoRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.List; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class AdminService { + private final RevInfoRepository revInfoRepository; + + public List getAllRevInfo(Long univId){ + return revInfoRepository.findAllByUnivId(univId).stream().map(r -> RevInfoDTO.of(r.getRev(), + LocalDateTime.ofInstant(Instant.ofEpochMilli(r.getRevTimeStamp()), ZoneId.systemDefault()), + r.getUnivId(), + r.getAction())).toList(); + } +} diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/setting/CustomReversionListener.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/setting/CustomReversionListener.java new file mode 100644 index 0000000..9af5ca4 --- /dev/null +++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/setting/CustomReversionListener.java @@ -0,0 +1,13 @@ +package com.softeer5.uniro_backend.admin.setting; + +import com.softeer5.uniro_backend.admin.entity.RevInfo; +import org.hibernate.envers.RevisionListener; + +public class CustomReversionListener implements RevisionListener { + @Override + public void newRevision(Object revisionEntity) { + RevInfo revinfo = (RevInfo) revisionEntity; + revinfo.setUnivId(RevisionContext.getUnivId()); + revinfo.setAction(RevisionContext.getAction()); + } +} \ No newline at end of file diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/setting/RevisionContext.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/setting/RevisionContext.java new file mode 100644 index 0000000..7bc21ee --- /dev/null +++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/setting/RevisionContext.java @@ -0,0 +1,26 @@ +package com.softeer5.uniro_backend.admin.setting; + +public class RevisionContext { + private static final ThreadLocal univIdHolder = new ThreadLocal<>(); + private static final ThreadLocal actionHolder = new ThreadLocal<>(); + + public static void setAction(String action) { + actionHolder.set(action); + } + + public static String getAction() { + return actionHolder.get(); + } + + public static void setUnivId(Long univId) { + univIdHolder.set(univId); + } + + public static Long getUnivId() { + return univIdHolder.get(); + } + + public static void clear() { + univIdHolder.remove(); + } +} diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/constant/UniroConst.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/constant/UniroConst.java index b565816..538dfb8 100644 --- a/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/constant/UniroConst.java +++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/constant/UniroConst.java @@ -3,4 +3,5 @@ public final class UniroConst { public static final String NODE_KEY_DELIMITER = " "; public static final int CORE_NODE_CONDITION = 3; + public static final int BEFORE_DEFAULT_ORDER = -1; } diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/node/entity/Node.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/node/entity/Node.java index e4264eb..8364034 100644 --- a/uniro_backend/src/main/java/com/softeer5/uniro_backend/node/entity/Node.java +++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/node/entity/Node.java @@ -6,6 +6,7 @@ import java.util.Map; import lombok.*; +import org.hibernate.envers.Audited; import org.locationtech.jts.geom.Point; import jakarta.persistence.Column; @@ -21,6 +22,7 @@ @AllArgsConstructor(access = AccessLevel.PRIVATE) @Getter @ToString +@Audited public class Node { @Id @@ -28,6 +30,7 @@ public class Node { private Long id; @NotNull + @Column(columnDefinition = "POINT SRID 4326") private Point coordinates; private double height; diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/route/controller/RouteApi.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/route/controller/RouteApi.java index 671d75a..9fcd150 100644 --- a/uniro_backend/src/main/java/com/softeer5/uniro_backend/route/controller/RouteApi.java +++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/route/controller/RouteApi.java @@ -22,7 +22,7 @@ public interface RouteApi { @ApiResponse(responseCode = "200", description = "모든 지도 조회 성공"), @ApiResponse(responseCode = "400", description = "EXCEPTION(임시)", content = @Content), }) - public ResponseEntity getAllRoutesAndNodes(@PathVariable("univId") Long univId); + ResponseEntity getAllRoutesAndNodes(@PathVariable("univId") Long univId); @Operation(summary = "위험&주의 요소 조회") @ApiResponses(value = { diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/route/entity/Route.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/route/entity/Route.java index edcfdb3..071a5ab 100644 --- a/uniro_backend/src/main/java/com/softeer5/uniro_backend/route/entity/Route.java +++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/route/entity/Route.java @@ -8,6 +8,8 @@ import com.softeer5.uniro_backend.resolver.CautionListConverter; import com.softeer5.uniro_backend.resolver.DangerListConverter; import com.softeer5.uniro_backend.node.entity.Node; +import org.hibernate.envers.Audited; +import org.hibernate.envers.RelationTargetAuditMode; import org.locationtech.jts.geom.LineString; import jakarta.persistence.Column; @@ -27,6 +29,7 @@ @Entity @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter +@Audited public class Route { @Id @@ -35,16 +38,18 @@ public class Route { private double cost; - @Column(columnDefinition = "geometry(LineString, 4326)") // WGS84 좌표계 + @Column(columnDefinition = "LINESTRING SRID 4326") // WGS84 좌표계 private LineString path; @ManyToOne(fetch = LAZY) @JoinColumn(referencedColumnName = "id", name = "node1_id") + @Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED) @NotNull private Node node1; @ManyToOne(fetch = LAZY) @JoinColumn(referencedColumnName = "id", name = "node2_id") + @Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED) @NotNull private Node node2; diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/route/service/RouteService.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/route/service/RouteService.java index 7572e53..6339f76 100644 --- a/uniro_backend/src/main/java/com/softeer5/uniro_backend/route/service/RouteService.java +++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/route/service/RouteService.java @@ -3,6 +3,8 @@ import java.util.*; import java.util.stream.Collectors; +import com.softeer5.uniro_backend.admin.annotation.RevisionOperation; +import com.softeer5.uniro_backend.admin.entity.RevisionOperationType; import com.softeer5.uniro_backend.common.error.ErrorCode; import com.softeer5.uniro_backend.common.exception.custom.DangerCautionConflictException; import com.softeer5.uniro_backend.common.exception.custom.InvalidMapException; @@ -215,6 +217,7 @@ public GetRiskResDTO getRisk(Long univId, double startLat, double startLng, doub return GetRiskResDTO.of(routeWithJoin); } + @RevisionOperation(RevisionOperationType.UPDATE_RISK) @Transactional public void updateRisk(Long univId, Long routeId, PostRiskReqDTO postRiskReqDTO) { Route route = routeRepository.findByIdAndUnivId(routeId, univId)