Merge pull request #2 from Gyubin-Han/feature/url-short
Feature/url short
This commit is contained in:
@@ -14,6 +14,7 @@
|
|||||||
### 구현 현황
|
### 구현 현황
|
||||||
- [x] 개발 환경 구축
|
- [x] 개발 환경 구축
|
||||||
- [x] DB 설계 및 Entity 구현
|
- [x] DB 설계 및 Entity 구현
|
||||||
|
- [x] HTTP(HTTPS) URL 검증 구현
|
||||||
- [x] URL 단축 알고리즘 구현
|
- [x] URL 단축 알고리즘 구현
|
||||||
- [ ] URL 단축 저장 및 조회 기능 구현
|
- [ ] URL 단축 저장 및 조회 기능 구현
|
||||||
- [ ] 테스트 및 배포
|
- [ ] 테스트 및 배포
|
||||||
12
sql/DDL.sql
12
sql/DDL.sql
@@ -6,7 +6,7 @@ DROP TABLE IF EXISTS url_map;
|
|||||||
DROP TABLE IF EXISTS users;
|
DROP TABLE IF EXISTS users;
|
||||||
|
|
||||||
CREATE TABLE `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_email` VARCHAR(100) NOT NULL,
|
||||||
`user_password` VARCHAR(30) NOT NULL,
|
`user_password` VARCHAR(30) NOT NULL,
|
||||||
`user_nickname` VARCHAR(20) NOT NULL,
|
`user_nickname` VARCHAR(20) NOT NULL,
|
||||||
@@ -15,14 +15,14 @@ CREATE TABLE `users`(
|
|||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE `url_map`(
|
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_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,
|
`url_map_is_active` TINYINT(1) NOT NULL,
|
||||||
`user_id` INT NULL,
|
`user_id` BIGINT NULL,
|
||||||
`url_map_click` INT NOT NULL DEFAULT 0,
|
`url_map_click` BIGINT NOT NULL DEFAULT 0,
|
||||||
`url_map_created_at` DATETIME NOT NULL,
|
`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,
|
`url_map_expired_at` DATETIME NULL,
|
||||||
PRIMARY KEY(url_map_id),
|
PRIMARY KEY(url_map_id),
|
||||||
FOREIGN KEY(user_id) REFERENCES users(user_id)
|
FOREIGN KEY(user_id) REFERENCES users(user_id)
|
||||||
|
|||||||
@@ -2,28 +2,36 @@ package be.gyu.urlShortener.entity;
|
|||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
import jakarta.persistence.Column;
|
||||||
import jakarta.persistence.Entity;
|
import jakarta.persistence.Entity;
|
||||||
import jakarta.persistence.GeneratedValue;
|
import jakarta.persistence.GeneratedValue;
|
||||||
import jakarta.persistence.GenerationType;
|
import jakarta.persistence.GenerationType;
|
||||||
import jakarta.persistence.Id;
|
import jakarta.persistence.Id;
|
||||||
import jakarta.persistence.JoinColumn;
|
import jakarta.persistence.JoinColumn;
|
||||||
import jakarta.persistence.ManyToOne;
|
import jakarta.persistence.ManyToOne;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Getter
|
@Getter
|
||||||
|
@Builder(toBuilder=true)
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
public class UrlMap {
|
public class UrlMap {
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue(strategy=GenerationType.IDENTITY)
|
@GeneratedValue(strategy=GenerationType.IDENTITY)
|
||||||
private int urlMapId;
|
private Long urlMapId;
|
||||||
private String urlMapOriginal;
|
private String urlMapOriginal;
|
||||||
private String urlMapShort;
|
private String urlMapShort;
|
||||||
private boolean urlMapIsActive;
|
private boolean urlMapIsActive;
|
||||||
@ManyToOne
|
@ManyToOne
|
||||||
@JoinColumn(name="user_id")
|
@JoinColumn(name="user_id")
|
||||||
private Users user;
|
private Users user;
|
||||||
private int urlMapClick;
|
@Column(insertable=false)
|
||||||
|
private Long urlMapClick;
|
||||||
private LocalDateTime urlMapCreatedAt;
|
private LocalDateTime urlMapCreatedAt;
|
||||||
private LocalDateTime urlMapEditedAt;
|
private LocalDateTime urlMapUpdatedAt;
|
||||||
private LocalDateTime urlMapExpiredAt;
|
private LocalDateTime urlMapExpiredAt;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,14 +6,16 @@ import jakarta.persistence.Entity;
|
|||||||
import jakarta.persistence.GeneratedValue;
|
import jakarta.persistence.GeneratedValue;
|
||||||
import jakarta.persistence.GenerationType;
|
import jakarta.persistence.GenerationType;
|
||||||
import jakarta.persistence.Id;
|
import jakarta.persistence.Id;
|
||||||
|
import lombok.Builder;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Getter
|
@Getter
|
||||||
|
@Builder
|
||||||
public class Users {
|
public class Users {
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue(strategy=GenerationType.IDENTITY)
|
@GeneratedValue(strategy=GenerationType.IDENTITY)
|
||||||
private int userId;
|
private Long userId;
|
||||||
private String userEmail;
|
private String userEmail;
|
||||||
private String userPassword;
|
private String userPassword;
|
||||||
private String userNickname;
|
private String userNickname;
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package be.gyu.urlShortener.exception;
|
||||||
|
|
||||||
|
public class ShortUrlNotFoundException extends RuntimeException{
|
||||||
|
public ShortUrlNotFoundException(){ super("존재하지 않는 단축 URL 입니다."); }
|
||||||
|
public ShortUrlNotFoundException(String msg){ super(msg); }
|
||||||
|
}
|
||||||
@@ -0,0 +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<UrlMap,Integer> {
|
||||||
|
public Optional<UrlMap> findByUrlMapShort(String urlMapShort);
|
||||||
|
}
|
||||||
95
src/main/java/be/gyu/urlShortener/service/MainService.java
Normal file
95
src/main/java/be/gyu/urlShortener/service/MainService.java
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
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.exception.ShortUrlNotFoundException;
|
||||||
|
import be.gyu.urlShortener.repository.UrlMapRepository;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 단축 URL로 원본 URL 조회 및 반환 메소드
|
||||||
|
public String getOriginalUrl(String shortUrl){
|
||||||
|
Optional<UrlMap> optional=urlMapRepository.findByUrlMapShort(shortUrl);
|
||||||
|
|
||||||
|
if(!optional.isPresent()){
|
||||||
|
throw new ShortUrlNotFoundException();
|
||||||
|
}
|
||||||
|
|
||||||
|
return optional.get().getUrlMapOriginal();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user