Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import com.factoreal.backend.domain.abnormalLog.dto.TargetType;
import com.factoreal.backend.domain.abnormalLog.dto.request.AbnormalPagingRequest;
import com.factoreal.backend.domain.abnormalLog.dto.response.AbnormalLogResponse;
import com.factoreal.backend.domain.sensor.dto.SensorKafkaDto;
import com.factoreal.backend.domain.sensor.entity.Sensor;
import com.factoreal.backend.domain.abnormalLog.entity.AbnormalLog;
import com.factoreal.backend.domain.sensor.dto.SensorKafkaDto;
import com.factoreal.backend.domain.zone.application.ZoneHistoryService;
Expand Down Expand Up @@ -50,6 +52,7 @@ public class AbnormalLogService {
* @throws Exception
*/
@Transactional(rollbackFor = Exception.class)

public AbnormalLog saveAbnormalLogFromSensorKafkaDto(
SensorKafkaDto sensorKafkaDto,
SensorType sensorType,
Expand All @@ -62,10 +65,10 @@ public AbnormalLog saveAbnormalLogFromSensorKafkaDto(
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "존재하지 않는 공간 ID: " + sensorKafkaDto.getZoneId());
}

log.info(">>>>>> zone : {} " ,zone);
log.info(">>>>>> zone : {} ", zone);

// DTO의 severity (AlarmEvent.RiskLevel)를 Entity RiskLevel로 매핑
// RiskLevel entityRiskLevel = mapDtoSeverityToEntityRiskLevel(riskLevel);
// RiskLevel entityRiskLevel = mapDtoSeverityToEntityRiskLevel(riskLevel);
// [TODO] 현재는 스프린트 1 웹 푸쉬, 대시보드 히트 맵 알림 로그만 구현되있음. worker, equip 로그용 구현 필요.
AbnormalLog abnormalLog = AbnormalLog.builder()
.targetId(sensorKafkaDto.getSensorId())
Expand Down Expand Up @@ -97,8 +100,7 @@ public AbnormalLog saveAbnormalLogFromWearableKafkaDto(
TargetType targetType
){
// workerId에 해당되는 사람이 제일 최근에 있던 공간 조회
ZoneHist zonehist = zoneHistoryService.
findByWorker_WorkerIdAndExistFlag(wearableKafkaDto.getWorkerId(), 1);
ZoneHist zonehist = zoneHistoryService.getCurrentWorkerLocation(wearableKafkaDto.getWorkerId());
Zone zone;
if (zonehist == null){
zone = zoneService.findByZoneId("00000000000000-000");
Expand Down Expand Up @@ -133,7 +135,8 @@ public Page<AbnormalLogResponse> findAllAbnormalLogsUnRead(AbnormalPagingRequest
return abnormalLogs.map(AbnormalLog::fromEntity);
}

public Page<AbnormalLogResponse> findAbnormalLogsByAbnormalType(AbnormalPagingRequest abnormalPagingRequest, String abnormalType){
public Page<AbnormalLogResponse> findAbnormalLogsByAbnormalType(AbnormalPagingRequest abnormalPagingRequest,
String abnormalType) {
// 한번에 DB전체를 주는 것이 아닌 구간 나눠서 전달하기 위함
Pageable pageable = getPageable(abnormalPagingRequest);
Page<AbnormalLog> abnormalLogs = abnLogRepository.findAbnormalLogsByAbnormalType(abnormalType,pageable);
Expand All @@ -158,16 +161,14 @@ public Page<AbnormalLogResponse> findAbnormalLogsByTargetId(AbnormalPagingReques
targetId,
pageable);
return abnormalLogs.map(
abn_log -> objectMapper.convertValue(abn_log, AbnormalLogResponse.class)
);
abn_log -> objectMapper.convertValue(abn_log, AbnormalLogResponse.class));
}


// FE에서 알람을 클릭한 경우 읽음으로 수정
@Transactional
public boolean readCheck(Long abnormalLogId){
public boolean readCheck(Long abnormalLogId) {
AbnormalLog abnormalLog = abnLogRepository.findById(abnormalLogId).orElse(null);
if(abnormalLog == null){
if (abnormalLog == null) {
return false;
}

Expand All @@ -176,18 +177,33 @@ public boolean readCheck(Long abnormalLogId){
readRequired();
return true;
}
@Transactional
public AbnormalLog saveAbnormalLog(SensorKafkaDto dto, Sensor sensor, int dangerLevel) {
RiskLevel riskLevel = RiskLevel.fromPriority(dangerLevel);

AbnormalLog abnormalLog = AbnormalLog.builder()
.targetId(dto.getSensorId())
.targetType(TargetType.Sensor)
.abnormalType(riskMessageProvider.getRiskMessageBySensor(sensor.getSensorType(), riskLevel))
.abnVal(dto.getVal())
.zone(sensor.getZone())
.detectedAt(LocalDateTime.parse(dto.getTime()))
.isRead(false)
.build();

return abnLogRepository.save(abnormalLog);
}
@Transactional(readOnly = true)
// 읽지 않은 알람이 몇개인지 반환
public Long readRequired(){
Long count = abnLogRepository.countByIsReadFalse();
// webSocketSender.sendUnreadCount(count);
public Long readRequired() {
Long count = abnLogRepository.countByIsReadFalse();
// webSocketSender.sendUnreadCount(count);
return count;
}

private Pageable getPageable(AbnormalPagingRequest abnormalPagingRequest){
private Pageable getPageable(AbnormalPagingRequest abnormalPagingRequest) {
return PageRequest.of(
abnormalPagingRequest.getPage(),
abnormalPagingRequest.getSize()
);
abnormalPagingRequest.getSize());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public class ControlLog {
private String controlType;

@Column(name = "control_val")
private Integer controlVal;
private Double controlVal;

@Column(name = "control_stat")
private Integer controlStat;
Expand All @@ -44,5 +44,4 @@ public class ControlLog {
@JoinColumn(name = "zone_id", referencedColumnName = "zone_id")
private Zone zone;


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.factoreal.backend.domain.controlLog.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import com.factoreal.backend.domain.controlLog.entity.ControlLog;

/**
* 자동제어 로그 저장을 위한 레포지토리
*/
public interface ControlLogRepository extends JpaRepository<ControlLog, Long> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package com.factoreal.backend.domain.controlLog.service;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.factoreal.backend.domain.abnormalLog.entity.AbnormalLog;
import com.factoreal.backend.domain.controlLog.entity.ControlLog;
import com.factoreal.backend.domain.controlLog.repository.ControlLogRepository;
import com.factoreal.backend.domain.zone.entity.Zone;
import com.factoreal.backend.messaging.mqtt.MqttPublishService;
import com.factoreal.backend.messaging.sender.WebSocketSender;

import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;

@Slf4j
@Service
@RequiredArgsConstructor
// 제어 로그 저장 및 알림 전송 서비스
public class ControlLogService {

private final ControlLogRepository controlLogRepository;
private final MqttPublishService mqttPublishService;
private final WebSocketSender webSocketSender;

@Transactional
public ControlLog saveControlLog(AbnormalLog abnormalLog, String controlType, Double controlVal, Integer controlStat,
Zone zone) {
ControlLog controlLog = ControlLog.builder()
.abnormalLog(abnormalLog)
.controlType(controlType)
.controlVal(controlVal)
.controlStat(controlStat)
.executedAt(LocalDateTime.now())
.zone(zone)
.build();

// 제어 로그 저장
ControlLog savedLog = controlLogRepository.save(controlLog);

// 발송 여부를 포함할 맵
Map<String, Boolean> deliveryStatus = new HashMap<>();

try {
// MQTT 메시지 발행
mqttPublishService.publishControlMessage(savedLog);
deliveryStatus.put("mqttDelivered", true);
} catch (Exception e) {
log.error("❌ MQTT 메시지 발행 실패: {}", e.getMessage(), e);
deliveryStatus.put("mqttDelivered", false);
}

try {
// WebSocket으로 제어 상태 전송 (발송 상태 포함)
webSocketSender.sendControlStatus(savedLog, deliveryStatus);

log.info("✅ 제어 로그 저장 및 알림 전송 완료: controlId={}, abnormalId={}, status={}",
savedLog.getId(), abnormalLog.getId(), deliveryStatus);
} catch (Exception e) {
log.error("❌ WebSocket 메시지 전송 실패: {}", e.getMessage(), e);
}

return savedLog;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,6 @@ public ResponseEntity<Void> update(
@PathVariable("sensorId") String sensorId,
@RequestBody SensorUpdateRequest sensorUpdateRequest) {
service.updateSensor(sensorId, sensorUpdateRequest);
return ResponseEntity.ok().build();
return ResponseEntity.ok().build(); // 204 응답 반환
}
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
package com.factoreal.backend.domain.worker.api;

import com.factoreal.backend.domain.worker.application.WorkerService;
import com.factoreal.backend.domain.worker.dto.request.CreateWorkerRequest;
import com.factoreal.backend.domain.worker.dto.response.WorkerDetailResponse;
import com.factoreal.backend.domain.worker.dto.response.WorkerInfoResponse;
import com.factoreal.backend.domain.worker.dto.response.ZoneManagerResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

Expand All @@ -22,26 +22,34 @@
@Tag(name = "작업자 API", description = "작업자 조회 API")
public class WorkerController {
private final WorkerService workerService;

@Operation(summary = "전체 작업자 목록 조회", description = "전체 작업자 목록을 조회합니다.")

@Operation(summary = "작업자 생성", description = "새로운 작업자를 생성하고 접근 가능한 공간들을 선택합니다.")
@PostMapping
public ResponseEntity<Void> createWorker(@RequestBody CreateWorkerRequest request) {
log.info("작업자 생성 요청: {}", request);
workerService.createWorker(request);
return ResponseEntity.ok().build(); // 작업자 생성 성공 시 200 응답
}

@Operation(summary = "전체 작업자 목록 조회", description = "전체 작업자 목록과 각 작업자의 상태 및 위치 정보를 조회합니다.")
@GetMapping
public List<WorkerDetailResponse> getAllWorkers() {
public ResponseEntity<List<WorkerDetailResponse>> getAllWorkers() {
log.info("전체 작업자 목록 조회 요청");
return workerService.getAllWorkers();
List<WorkerDetailResponse> workers = workerService.getAllWorkers();
return ResponseEntity.ok(workers);
}

@Operation(summary = "공간별 작업자 목록 조회", description = "공간 ID를 기반으로 현재 해당 공간에 들어가있는 작업자 리스트를 조회합니다.")
@GetMapping("/zone/{zoneId}")
public List<WorkerInfoResponse> getWorkersByZoneId(@PathVariable String zoneId) {
log.info("공간 ID: {}의 작업자 목록 조회 요청", zoneId);
return workerService.getWorkersByZoneId(zoneId);
}

@Operation(summary = "공간 담당자 정보 조회",
description = "공간 ID를 기반으로 해당 공간의 담당자와 현재 위치 정보를 조회합니다.")
@Operation(summary = "공간 담당자와 담당자의 현재 위치정보 조회", description = "공간 ID를 기반으로 해당 공간의 담당자와 현재 위치 정보를 조회합니다.")
@GetMapping("/zone/{zoneId}/manager")
public ZoneManagerResponse getZoneManager(@PathVariable String zoneId) {
log.info("공간 ID: {}의 담당자 정보 조회 요청", zoneId);
return workerService.getZoneManagerWithLocation(zoneId);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.factoreal.backend.domain.worker.api;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import com.factoreal.backend.domain.worker.application.WorkerManagerService;
import com.factoreal.backend.domain.worker.dto.response.WorkerManagerResponse;

import java.util.List;

@Tag(name = "공간 담당자 API", description = "공간별 담당자 관리 API")
@Slf4j
@RestController
@RequestMapping("/api/zone-managers")
@RequiredArgsConstructor
public class WorkerManagerController {

private final WorkerManagerService workerManagerService;

@Operation(summary = "공간 담당자 후보 목록 조회", description = "특정 공간의 담당자로 지정 가능한 작업자 목록을 조회합니다.")
@GetMapping("/candidates/{zoneId}")
public ResponseEntity<List<WorkerManagerResponse>> getManagerCandidates(
@Parameter(description = "공간 ID", required = true) @PathVariable String zoneId) {
log.info("공간 ID: {}의 담당자 후보 목록 조회 요청", zoneId);
return ResponseEntity.ok(workerManagerService.getManagerCandidates(zoneId));
}

@Operation(summary = "공간 담당자 지정", description = "특정 공간의 담당자를 지정합니다.")
@PostMapping("/{zoneId}/assign/{workerId}")
public ResponseEntity<Void> assignManager(
@Parameter(description = "공간 ID", required = true) @PathVariable String zoneId,
@Parameter(description = "작업자 ID", required = true) @PathVariable String workerId) {
log.info("공간 ID: {}의 담당자를 작업자 ID: {}로 지정 요청", zoneId, workerId);
workerManagerService.assignManager(zoneId, workerId);
return ResponseEntity.ok().build();
}

@Operation(summary = "현재 공간 담당자 정보 조회", description = "특정 공간의 현재 담당자 정보를 조회합니다.")
@GetMapping("/{zoneId}")
public ResponseEntity<WorkerManagerResponse> getCurrentManager(
@Parameter(description = "공간 ID", required = true) @PathVariable String zoneId) {
log.info("공간 ID: {}의 현재 담당자 조회 요청", zoneId);
WorkerManagerResponse manager = workerManagerService.getCurrentManager(zoneId);
return manager != null ? ResponseEntity.ok(manager) : ResponseEntity.noContent().build();
// 담당자가 있으면 담당자 정보 반환, 없으면 빈 응답
}
}
Loading