From 77b78282a1286712bc1983a8152a3715a8e2c716 Mon Sep 17 00:00:00 2001 From: Gyubin-Han Date: Tue, 10 Jun 2025 17:43:35 +0900 Subject: [PATCH 1/5] =?UTF-8?q?Add:=20UrlMap=20Repository=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../be/gyu/urlShortener/repository/UrlMapRepository.java | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/main/java/be/gyu/urlShortener/repository/UrlMapRepository.java diff --git a/src/main/java/be/gyu/urlShortener/repository/UrlMapRepository.java b/src/main/java/be/gyu/urlShortener/repository/UrlMapRepository.java new file mode 100644 index 0000000..3a9f2d4 --- /dev/null +++ b/src/main/java/be/gyu/urlShortener/repository/UrlMapRepository.java @@ -0,0 +1,9 @@ +package be.gyu.urlShortener.repository; + +import org.springframework.data.jpa.repository.JpaRepository; + +import be.gyu.urlShortener.entity.UrlMap; + +public interface UrlMapRepository extends JpaRepository { + +} From 182d065b37c2235fea76460394396bb63bced0ff Mon Sep 17 00:00:00 2001 From: Gyubin-Han Date: Thu, 12 Jun 2025 18:43:14 +0900 Subject: [PATCH 2/5] =?UTF-8?q?Chore:=20DB=20-=20=EA=B0=81=20ID=20?= =?UTF-8?q?=EC=BB=AC=EB=9F=BC=EC=9D=98=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20?= =?UTF-8?q?=ED=83=80=EC=9E=85=20=EB=B0=8F=20Entity=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ReadMe.md | 3 ++- sql/DDL.sql | 12 ++++++------ src/main/java/be/gyu/urlShortener/entity/UrlMap.java | 10 +++++++--- src/main/java/be/gyu/urlShortener/entity/Users.java | 4 +++- 4 files changed, 18 insertions(+), 11 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index fa1c7e4..44d2376 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -14,6 +14,7 @@ ### 구현 현황 - [x] 개발 환경 구축 - [x] DB 설계 및 Entity 구현 -- [x] URL 단축 알고리즘 구현 +- [x] HTTP(HTTPS) URL 검증 구현 +- [ ] URL 단축 알고리즘 구현 - [ ] URL 단축 저장 및 조회 기능 구현 - [ ] 테스트 및 배포 \ No newline at end of file diff --git a/sql/DDL.sql b/sql/DDL.sql index 732b0ef..08c64c0 100644 --- a/sql/DDL.sql +++ b/sql/DDL.sql @@ -6,7 +6,7 @@ DROP TABLE IF EXISTS url_map; DROP TABLE IF EXISTS users; CREATE TABLE `users`( - `user_id` INT NOT NULL AUTO_INCREMENT, + `user_id` BIGINT NOT NULL AUTO_INCREMENT, `user_email` VARCHAR(100) NOT NULL, `user_password` VARCHAR(30) NOT NULL, `user_nickname` VARCHAR(20) NOT NULL, @@ -15,14 +15,14 @@ CREATE TABLE `users`( ); CREATE TABLE `url_map`( - `url_map_id` INT NOT NULL AUTO_INCREMENT, + `url_map_id` BIGINT NOT NULL AUTO_INCREMENT, `url_map_original` VARCHAR(255) NOT NULL, - `url_map_short` VARCHAR(7) NOT NULL, + `url_map_short` VARCHAR(20) NOT NULL, `url_map_is_active` TINYINT(1) NOT NULL, - `user_id` INT NULL, - `url_map_click` INT NOT NULL DEFAULT 0, + `user_id` BIGINT NULL, + `url_map_click` BIGINT NOT NULL DEFAULT 0, `url_map_created_at` DATETIME NOT NULL, - `url_map_edited_at` DATETIME NOT NULL, + `url_map_updated_at` DATETIME NOT NULL, `url_map_expired_at` DATETIME NULL, PRIMARY KEY(url_map_id), FOREIGN KEY(user_id) REFERENCES users(user_id) diff --git a/src/main/java/be/gyu/urlShortener/entity/UrlMap.java b/src/main/java/be/gyu/urlShortener/entity/UrlMap.java index 0fcb4cc..0081cbd 100644 --- a/src/main/java/be/gyu/urlShortener/entity/UrlMap.java +++ b/src/main/java/be/gyu/urlShortener/entity/UrlMap.java @@ -2,28 +2,32 @@ package be.gyu.urlShortener.entity; import java.time.LocalDateTime; +import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; +import lombok.Builder; import lombok.Getter; @Entity @Getter +@Builder(toBuilder=true) public class UrlMap { @Id @GeneratedValue(strategy=GenerationType.IDENTITY) - private int urlMapId; + private Long urlMapId; private String urlMapOriginal; private String urlMapShort; private boolean urlMapIsActive; @ManyToOne @JoinColumn(name="user_id") private Users user; - private int urlMapClick; + @Column(insertable=false) + private Long urlMapClick; private LocalDateTime urlMapCreatedAt; - private LocalDateTime urlMapEditedAt; + private LocalDateTime urlMapUpdatedAt; private LocalDateTime urlMapExpiredAt; } diff --git a/src/main/java/be/gyu/urlShortener/entity/Users.java b/src/main/java/be/gyu/urlShortener/entity/Users.java index d257e60..e774dd0 100644 --- a/src/main/java/be/gyu/urlShortener/entity/Users.java +++ b/src/main/java/be/gyu/urlShortener/entity/Users.java @@ -6,14 +6,16 @@ import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; +import lombok.Builder; import lombok.Getter; @Entity @Getter +@Builder public class Users { @Id @GeneratedValue(strategy=GenerationType.IDENTITY) - private int userId; + private Long userId; private String userEmail; private String userPassword; private String userNickname; From 400f325274d72ac69f76391bb49a7548d8900e2b Mon Sep 17 00:00:00 2001 From: Gyubin-Han Date: Sun, 15 Jun 2025 14:43:05 +0900 Subject: [PATCH 3/5] =?UTF-8?q?Feat:=20URL=20=EB=8B=A8=EC=B6=95=20?= =?UTF-8?q?=EC=95=8C=EA=B3=A0=EB=A6=AC=EC=A6=98(=EB=A1=9C=EC=A7=81)=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ReadMe.md | 2 +- .../gyu/urlShortener/service/MainService.java | 82 +++++++++++++++++++ 2 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 src/main/java/be/gyu/urlShortener/service/MainService.java diff --git a/ReadMe.md b/ReadMe.md index 44d2376..631addd 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -15,6 +15,6 @@ - [x] 개발 환경 구축 - [x] DB 설계 및 Entity 구현 - [x] HTTP(HTTPS) URL 검증 구현 -- [ ] URL 단축 알고리즘 구현 +- [x] URL 단축 알고리즘 구현 - [ ] URL 단축 저장 및 조회 기능 구현 - [ ] 테스트 및 배포 \ No newline at end of file diff --git a/src/main/java/be/gyu/urlShortener/service/MainService.java b/src/main/java/be/gyu/urlShortener/service/MainService.java new file mode 100644 index 0000000..b574d81 --- /dev/null +++ b/src/main/java/be/gyu/urlShortener/service/MainService.java @@ -0,0 +1,82 @@ +package be.gyu.urlShortener.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import be.gyu.urlShortener.entity.UrlMap; +import be.gyu.urlShortener.repository.UrlMapRepository; + +import java.time.LocalDateTime; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +import io.seruco.encoding.base62.Base62; + +@Service +public class MainService { + @Autowired + private UrlMapRepository urlMapRepository; + + // 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.\\/가-힣\\-\\ ?=&:]*"; + // Base62 Encoder Instance 생성 및 호출 + private Base62 base62=Base62.createInstance(); + + // HTTP(S) URL 검증 메소드 + public boolean validOriginalUrl(String url){ + return url.matches(urlRegPattern); + } + + // URL 단축 메소드 + public String createUrlShort(String url){ + // SHA-256 Hashing을 위해 Instance 생성 및 호출 + MessageDigest md; + try{ + md=MessageDigest.getInstance("sha256"); + }catch(NoSuchAlgorithmException e){ + e.printStackTrace(); + throw new RuntimeException(e); + } + + // DB에 저장하기 위해 Entity 객체 정의 + UrlMap urlMap=UrlMap.builder() + .urlMapOriginal(url) + .urlMapShort("") + .urlMapIsActive(false) + .urlMapClick(0l) + .urlMapCreatedAt(LocalDateTime.now()) + .urlMapUpdatedAt(LocalDateTime.now()) + .build(); + + // DB에 저장 - 초기 저장 (단축 URL은 빈 값으로 우선 저장) + urlMapRepository.save(urlMap); + + // 고유 값 생성 + // 고유한 값은 DB의 ID 값 + 현재 시간을 결합한 문자열을 SHA-256으로 해싱한 후, + // Base62로 인코딩 진행 + long id=urlMap.getUrlMapId(); + String urlMapIdString=String.format("%011d",id); + String nowDateTimeString=LocalDateTime.now().toString(); + String result=urlMapIdString+nowDateTimeString; + + // Base62 Encoding + byte[] barr=base62.encode(md.digest(result.getBytes())); + StringBuilder sb=new StringBuilder(); + for(byte b : barr){ + sb.append(String.format("%c",b)); + } + + // 해싱 및 인코딩된 문장을 7자리 추출하여 저장 + String shortResult=sb.toString().substring(0,7); + urlMap=urlMap.toBuilder() + .urlMapShort(shortResult) + .build(); + + // 최종 저장 - 단축된 URL도 포함하여 저장 + // (기존 데이터에 Update하는 방법으로 저장) + urlMapRepository.save(urlMap); + + // 단축된 URL 반환 + return shortResult; + } +} From 0d2a58f60e949ba0900cb5c8bff7b61996e26665 Mon Sep 17 00:00:00 2001 From: Gyubin-Han Date: Tue, 17 Jun 2025 15:54:24 +0900 Subject: [PATCH 4/5] =?UTF-8?q?Fix:=20=EB=8B=A8=EC=B6=95=20URL=20Update=20?= =?UTF-8?q?=EC=8B=9C,=20=EC=A0=80=EC=9E=A5=EB=90=98=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EA=B3=A0=20=EC=98=88=EC=99=B8=20=EB=B0=9C=EC=83=9D=ED=95=98?= =?UTF-8?q?=EB=8A=94=20=EB=AC=B8=EC=A0=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/be/gyu/urlShortener/entity/UrlMap.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/be/gyu/urlShortener/entity/UrlMap.java b/src/main/java/be/gyu/urlShortener/entity/UrlMap.java index 0081cbd..a746754 100644 --- a/src/main/java/be/gyu/urlShortener/entity/UrlMap.java +++ b/src/main/java/be/gyu/urlShortener/entity/UrlMap.java @@ -9,12 +9,16 @@ import jakarta.persistence.GenerationType; 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(toBuilder=true) +@NoArgsConstructor +@AllArgsConstructor public class UrlMap { @Id @GeneratedValue(strategy=GenerationType.IDENTITY) From 673ba0176a31c9052810f227be6731e087ebd3cf Mon Sep 17 00:00:00 2001 From: Gyubin-Han Date: Tue, 17 Jun 2025 16:04:09 +0900 Subject: [PATCH 5/5] =?UTF-8?q?Feat:=20=EB=8B=A8=EC=B6=95=20URL=EB=A1=9C?= =?UTF-8?q?=20=EC=9B=90=EB=B3=B8=20URL=20=EC=A1=B0=ED=9A=8C=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/ShortUrlNotFoundException.java | 6 ++++++ .../urlShortener/repository/UrlMapRepository.java | 6 ++++-- .../be/gyu/urlShortener/service/MainService.java | 13 +++++++++++++ 3 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 src/main/java/be/gyu/urlShortener/exception/ShortUrlNotFoundException.java diff --git a/src/main/java/be/gyu/urlShortener/exception/ShortUrlNotFoundException.java b/src/main/java/be/gyu/urlShortener/exception/ShortUrlNotFoundException.java new file mode 100644 index 0000000..b5bd276 --- /dev/null +++ b/src/main/java/be/gyu/urlShortener/exception/ShortUrlNotFoundException.java @@ -0,0 +1,6 @@ +package be.gyu.urlShortener.exception; + +public class ShortUrlNotFoundException extends RuntimeException{ + public ShortUrlNotFoundException(){ super("존재하지 않는 단축 URL 입니다."); } + public ShortUrlNotFoundException(String msg){ super(msg); } +} diff --git a/src/main/java/be/gyu/urlShortener/repository/UrlMapRepository.java b/src/main/java/be/gyu/urlShortener/repository/UrlMapRepository.java index 3a9f2d4..c741119 100644 --- a/src/main/java/be/gyu/urlShortener/repository/UrlMapRepository.java +++ b/src/main/java/be/gyu/urlShortener/repository/UrlMapRepository.java @@ -1,9 +1,11 @@ package be.gyu.urlShortener.repository; +import java.util.Optional; + import org.springframework.data.jpa.repository.JpaRepository; import be.gyu.urlShortener.entity.UrlMap; public interface UrlMapRepository extends JpaRepository { - -} + public Optional findByUrlMapShort(String urlMapShort); +} \ No newline at end of file diff --git a/src/main/java/be/gyu/urlShortener/service/MainService.java b/src/main/java/be/gyu/urlShortener/service/MainService.java index b574d81..72b9b40 100644 --- a/src/main/java/be/gyu/urlShortener/service/MainService.java +++ b/src/main/java/be/gyu/urlShortener/service/MainService.java @@ -4,8 +4,10 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import be.gyu.urlShortener.entity.UrlMap; +import be.gyu.urlShortener.exception.ShortUrlNotFoundException; import be.gyu.urlShortener.repository.UrlMapRepository; +import java.util.Optional; import java.time.LocalDateTime; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -79,4 +81,15 @@ public class MainService { // 단축된 URL 반환 return shortResult; } + + // 단축 URL로 원본 URL 조회 및 반환 메소드 + public String getOriginalUrl(String shortUrl){ + Optional optional=urlMapRepository.findByUrlMapShort(shortUrl); + + if(!optional.isPresent()){ + throw new ShortUrlNotFoundException(); + } + + return optional.get().getUrlMapOriginal(); + } }