3주차. JWT 토큰 기반의 회원 가입, 로그인 구현 , 이미지 저장을 위한 AWS S3 연결
💡 이미지 저장을 위한 AWS S3 연결
회원 가입시, 프로필 이미지 저장을 위해 AWS S3를 연결하였다.
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import com.amazonaws.services.s3.model.CannedAccessControlList;
import com.amazonaws.services.s3.model.ObjectMetadata;
import com.amazonaws.services.s3.model.PutObjectRequest;
import jakarta.annotation.PostConstruct;
import lombok.NoArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
@Service
@NoArgsConstructor
public class S3Service implements ApplicationListener<ApplicationStartedEvent> {
private AmazonS3 s3Client;
@Value("${cloud.aws.credentials.accessKey}") // secrefile.yml에서 해당 key 지정
private String accessKey;
@Value("${cloud.aws.credentials.secretKey}")
private String secretKey;
@Value("${cloud.aws.s3.bucket}")
private String bucket;
@Value("${cloud.aws.region.static}")
private String region;
public static final String CLOUD_FRONT_DOMAIN_NAME = "https://도메인 네임";
@PostConstruct
public void setS3Client() {
AWSCredentials credentials = new BasicAWSCredentials(this.accessKey, this.secretKey);
s3Client = AmazonS3ClientBuilder.standard()
.withCredentials(new AWSStaticCredentialsProvider(credentials))
.withRegion(this.region)
.build();
}
public String upload(MultipartFile file) throws IOException {
String contentType = file.getContentType();
// 확장자가 jpeg, jpg, gif, png가 아니면 업로드 실패
if(!(contentType.contains("image/jpeg")||contentType.contains("image/jpg")||contentType.contains("image/png")||contentType.contains("image/gif"))) {
throw new IllegalArgumentException("이미지 확장자가 올바르지 않습니다.");
}
// 고유한 key 값을 갖기위해 현재 시간을 postfix로 붙여줌
SimpleDateFormat date = new SimpleDateFormat("yyyymmddHHmmss");
String fileName = file.getOriginalFilename() + "-" + date.format(new Date());
ObjectMetadata metadata = new ObjectMetadata();
metadata.setContentType(file.getContentType());
metadata.setContentLength(file.getSize());
// 파일 업로드
s3Client.putObject(new PutObjectRequest(bucket, fileName, file.getInputStream(), metadata)
.withCannedAcl(CannedAccessControlList.PublicRead));
return fileName;
}
@Override
public void onApplicationEvent(ApplicationStartedEvent event) {
}
}
이미지만 저장하기 위해, 확장자를 jpg, jpeg, png, gif로 제한하였고, 저장된 파일 경로가 반환되도록 지정했다.
💡 회원 가입 및 로그인 구현
Spring Security : Remember Me 인증 방식을 이용하여 구현 - 데이터 베이스 사용
Remember Me 인증이란?
remember-me 또는 persistent-login 인증은 웹 사이트가 세션 간에 보안 주체의 ID를 기억할 수 있는 것을 말한다.이는 일반적으로 브라우저에 쿠키를 전송하여 수행되며, 이후 세션 중에 쿠키가 감지되어 자동 로그인이 발생한다. Spring Security는 이러한 작업을 수행하는데 필요한 후크를 제공하며 두 가지 구체적인 remember-me 구현이 있다.
하나는 해시를 사용하여 쿠키 기반 토큰의 보안을 유지하고, 가른 하나는 데이터베이스 또는 기타 영구 저장 메커니즘을 사용하여 생성된 토큰을 저장한다.
이번 구현에는 DB를 이용하여 영구 토큰 접근 방식을 사용하였다.
Remember-Me Authentication :: Spring Security
Remember-Me Authentication :: Spring Security
Remember-me or persistent-login authentication refers to web sites being able to remember the identity of a principal between sessions. This is typically accomplished by sending a cookie to the browser, with the cookie being detected during future sessions
docs.spring.io
4주차. 날짜별 단위 주택용 전력 요금 추출 API 구현, 유스케이스 작성
💡 날짜별 단위 주택용 전력 요금 추출 API 구현
전력 요금 계산을 위해 해당 도시의 단위 주택용 전력 요금을 추출하는 API를 구현했다.
아래의 전력 데이터 개방 포털 시스템 사이트의 Open API를 이용하였다.
전력데이터 개방 포털시스템
API 응답 예시(JSON) "data" : [ { "year": "2020", "month": "11", "metro": "서울특별시", "city": "종로구", "cntr": "가로등", "custCnt": 10287, "powerUsage": 793099, "bill": 87834879, "unitCost": 110.7 } ,{ "year": "2020", "month": "11", "metro
bigdata.kepco.co.kr
< service >
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import energypa.bems.unitCost.domain.UnitCost;
import energypa.bems.unitCost.dto.UnitCostDto;
import lombok.extern.slf4j.Slf4j;
import net.minidev.json.JSONArray;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestMapping;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.*;
@Slf4j
@Service
@RequestMapping
public class ApiService {
@Value("${openApi}")
private String API_KEY;
private final String REQUEST_URL = "https://bigdata.kepco.co.kr/openapi/v1/powerUsage/contractType.do";
private final SimpleDateFormat DATE_FMT = new SimpleDateFormat("yyyyMMdd");
public String makeQueryString(Map<String, String> paramMap) {
final StringBuilder sb = new StringBuilder();
paramMap.entrySet().forEach(( entry )->{
if( sb.length() > 0 ) {
sb.append('&');
}
sb.append(entry.getKey()).append('=').append(entry.getValue());
});
return sb.toString();
}
private LinkedHashMap<String, String> paramMapSet(LinkedHashMap<String, String> paramMap,UnitCostDto unitCostDto) {
paramMap.put("year" , unitCostDto.getYear().toString()); // 조회연도(YYYY)
if(unitCostDto.getMonth()<10){
paramMap.put("month" , "0" + unitCostDto.getMonth()); // 조회월(MM)
}
else{
paramMap.put("month", unitCostDto.getMonth().toString());
}
paramMap.put("metroCd" , unitCostDto.getMetroCd().toString()); // 시도코드
paramMap.put("cityCd" , unitCostDto.getCityCd().toString()); // 시군구코드
paramMap.put("cntrCd" , "100"); // 계약종별(주택용)
paramMap.put("apiKey" , API_KEY); // 발급받은 인증키
paramMap.put("returnType" , "json"); // 응답포맷
return paramMap;
}
public UnitCost getApi(UnitCostDto unitCostDto) throws IOException {
LinkedHashMap<String, String> paramMap = new LinkedHashMap<String, String>();
paramMap = paramMapSet(paramMap,unitCostDto);
JSONArray result;
try {
// Request URL 연결 객체 생성
URL requestURL = new URL(REQUEST_URL + "?" + makeQueryString(paramMap)); // 쿼리문 완성
log.info("url={}", requestURL);
HttpURLConnection conn = (HttpURLConnection) requestURL.openConnection();
// GET 방식으로 요청
conn.setRequestMethod("GET");
conn.setRequestProperty("Content-type", "application/json");
conn.setDoInput(true);
// 응답(Response) 구조 작성
// Stream -> JSONObject
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"));
String readline;
StringBuilder response = new StringBuilder();
while ((readline = br.readLine()) != null) {
response.append(readline);
}
br.close();
conn.disconnect();
// JSON 객체로 변환
ObjectMapper objectMapper = new ObjectMapper();
Map<String, Object> map = new HashMap<>();
map= objectMapper.readValue(response.toString(), new TypeReference<Map<String,Object>>(){});
List<Map<String, Object>> unitCostList = (List<Map<String, Object>>) map.get("data");
for(Map<String, Object> item :unitCostList){
UnitCost unitCost = new UnitCost((String) item.get("year"),(String) item.get("month"),(String) item.get("metro"),(String) item.get("city"),(String) item.get("cntr"),Double.valueOf(item.get("unitCost").toString()));
return unitCost;
}
} catch (IOException e) {
throw new IllegalArgumentException("open API 정보를 불러오는데 실패했습니다.");
}
return null;
}
}
< controller >
@RestController
@RequiredArgsConstructor
@Tag(name="unitCost", description = "전력 단위 요금 불러오는 API")
public class UnitCostController {
private final ApiService apiService;
@Operation(method = "get", summary = "해당 시간의 단위 주택용 전력 요금 추출 API")
@GetMapping("/unitCost") // 전력 단위 요금
public UnitCost unitCost(@ModelAttribute("unitCostDto") UnitCostDto unitCostDto) throws IOException {
return apiService.getApi(unitCostDto);
}
}
json형식의 데이터를 읽어와서, 필요한 정보만 추출하여 프론트에게 넘기도록 구현
< swagger > 다음과 같이 년도, 월, 시도 코드, 시군구별 코드를 입력하면
해당 시간 도시의 단위 전력 요금이 추출된다.
💡 유스케이스 작성
유스케이스를 시스템 사용자 측면에서 시스템 관리자, 건물 관리자, 건물 사용자와
프로그램 측면에서 서버, 클라이언트, 인공지능으로 작성하였다.
유스케이스 액터에 프로그램 측면이 들어가는지 몰랐는데 이번 기회에 알게 되었다.
3~4 주차 후기
1차 유스케이스 작성 후, 주요 기능인 ESS 배터리 스케줄링과 모니터링 기능에 대해서 깊게 작성하는게 좋을 것 같다는 교수님의 피드백을 받아서 부가적인 기능을 빼고, 주요 기능에 대하여 API와 자료형 크기까지 모두 정하였다.
유스케이스 작성할 때는 문서를 쓰고 수정하는 것이 번거롭다고 생각했는데, API에 담길 데이터까지 모두 정하였더니, AI와의 협업에 대해 더 자세히 알 수 있었고, 진행이 수월해질 것으로 기대한다.
'프로젝트 > DC 빌딩 관리 및 에너지 예측 시스템' 카테고리의 다른 글
[9~10 주차] 프로젝트 진행 과정 (ESS 전력 분배 모니터링 서비스) (1) | 2023.11.03 |
---|---|
[7~8 주차] 프로젝트 진행 과정 (건물 관리자 신청과 등록 API 구현, 전력 고지서 API, RDS 데이터 저장 ) (0) | 2023.10.29 |
[5~6 주차] 프로젝트 진행 과정 ( 데이터 저장, SSE 방식을 이용한 알림 서비스) (1) | 2023.10.29 |
[1~2 주차] 프로젝트 진행 과정 (주제 선정, 제안서) (1) | 2023.10.03 |
댓글