Merge pull request #20 from Gyubin-Han/feature/url-short

Feat: 단축 URL로 접속 시, 클라이언트 정보 저장 기능 구현
This commit is contained in:
Gyubin Han
2025-07-01 16:36:44 +09:00
committed by GitHub
4 changed files with 61 additions and 4 deletions

View File

@@ -57,9 +57,13 @@ public class MainController {
* @param response Re-Direct 응답 처리를 위한 매개변수 * @param response Re-Direct 응답 처리를 위한 매개변수
*/ */
@GetMapping("/{shortUrl}") @GetMapping("/{shortUrl}")
public void getShortUrl(@PathVariable(name="shortUrl") String shortUrl, HttpServletResponse response){ public void getShortUrl(@PathVariable(name="shortUrl") String shortUrl, HttpServletRequest request, HttpServletResponse response){
// 접속 정보들 추출
String userAgent=request.getHeader("User-Agent");
String userIpAddr=request.getRemoteAddr();
// 단축 URL을 통해, 원본 URL을 가져옴. // 단축 URL을 통해, 원본 URL을 가져옴.
String originalUrl=mainService.getOriginalUrl(shortUrl); String originalUrl=mainService.getOriginalUrl(shortUrl,userAgent,userIpAddr);
// 가져온 원본 URL 값을 적잘한 URL이 될 수 있도록 Re-Direct하기 전에 URL-Encoding // 가져온 원본 URL 값을 적잘한 URL이 될 수 있도록 Re-Direct하기 전에 URL-Encoding
String encodeUrl=UriComponentsBuilder.fromUriString(originalUrl).build().encode().toUriString(); String encodeUrl=UriComponentsBuilder.fromUriString(originalUrl).build().encode().toUriString();
// Re-Direct 상태 코드 설정 // Re-Direct 상태 코드 설정

View File

@@ -0,0 +1,28 @@
package be.gyu.urlShortener.entity;
import java.time.LocalDateTime;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Entity
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ClickStat {
@Id
private Long clickStatId;
@ManyToOne
@JoinColumn(name="url_map_id")
private UrlMap urlMap;
private LocalDateTime clickStatClickedAt;
private String clickStatUserAgent;
private String clickStatIpAddr;
}

View File

@@ -0,0 +1,11 @@
package be.gyu.urlShortener.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import be.gyu.urlShortener.entity.ClickStat;
@Repository
public interface ClickStatRepository extends JpaRepository<ClickStat,Long>{
}

View File

@@ -3,9 +3,11 @@ package be.gyu.urlShortener.service;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import be.gyu.urlShortener.entity.ClickStat;
import be.gyu.urlShortener.entity.UrlMap; import be.gyu.urlShortener.entity.UrlMap;
import be.gyu.urlShortener.exception.ShortUrlNotFoundException; import be.gyu.urlShortener.exception.ShortUrlNotFoundException;
import be.gyu.urlShortener.exception.UrlValidationFailedException; import be.gyu.urlShortener.exception.UrlValidationFailedException;
import be.gyu.urlShortener.repository.ClickStatRepository;
import be.gyu.urlShortener.repository.UrlMapRepository; import be.gyu.urlShortener.repository.UrlMapRepository;
import java.util.HashMap; import java.util.HashMap;
@@ -21,6 +23,8 @@ import io.seruco.encoding.base62.Base62;
public class MainService { public class MainService {
@Autowired @Autowired
private UrlMapRepository urlMapRepository; private UrlMapRepository urlMapRepository;
@Autowired
private ClickStatRepository clickStatRepository;
// HTTP(S) URL 검증 패턴식 // HTTP(S) URL 검증 패턴식
private final String urlRegPattern="^((http|https):\\/\\/)?([a-z0-9-]{2,}\\.[a-z]{2,}|([0-9]{1,3}\\.){3}[0-9]{1,3})[\\w.\\/가-힣\\-\\ ?=&:%0-9A-Fa-f]*"; private final String urlRegPattern="^((http|https):\\/\\/)?([a-z0-9-]{2,}\\.[a-z]{2,}|([0-9]{1,3}\\.){3}[0-9]{1,3})[\\w.\\/가-힣\\-\\ ?=&:%0-9A-Fa-f]*";
@@ -89,14 +93,24 @@ public class MainService {
} }
// 단축 URL로 원본 URL 조회 및 반환 메소드 // 단축 URL로 원본 URL 조회 및 반환 메소드
public String getOriginalUrl(String shortUrl){ public String getOriginalUrl(String shortUrl, String userAgent, String ipAddr){
Optional<UrlMap> optional=urlMapRepository.findByUrlMapShort(shortUrl); Optional<UrlMap> optional=urlMapRepository.findByUrlMapShort(shortUrl);
if(!optional.isPresent()){ if(!optional.isPresent()){
throw new ShortUrlNotFoundException(); throw new ShortUrlNotFoundException();
} }
return optional.get().getUrlMapOriginal(); // 접속 정보 저장을 위해서 새 엔티티 객체 생성
ClickStat clickStat=ClickStat.builder()
.clickStatClickedAt(LocalDateTime.now())
.urlMap(optional.get())
.clickStatUserAgent(userAgent)
.clickStatIpAddr(ipAddr)
.build();
clickStatRepository.save(clickStat);
return clickStat.getUrlMap().getUrlMapOriginal();
} }
// 단축 URL 생성 메소드 (검증 및 생성) // 단축 URL 생성 메소드 (검증 및 생성)