Skip to content

Commit

Permalink
Merge pull request #80 from softeerbootcamp4th/feat/#76-shorten-url
Browse files Browse the repository at this point in the history
Feat/#76 shorten url
  • Loading branch information
wjddn2165 authored Aug 11, 2024
2 parents 286b101 + 189fa19 commit 8871ce3
Show file tree
Hide file tree
Showing 15 changed files with 235 additions and 111 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public class LotteryParticipants {
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@OneToOne
@OneToOne // mappedBy 이용하면 둘 다 저장 안해도 됨
@JoinColumn(name = "base_user_id")
//todo: 왜이런지 알아보기
@JsonBackReference
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,44 @@
package JGS.CasperEvent.domain.url.controller;

import JGS.CasperEvent.domain.url.dto.ShortenUrlResponseDto;
import JGS.CasperEvent.domain.url.service.UrlService;
import JGS.CasperEvent.global.entity.BaseUser;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

@RestController
@RequestMapping("link")
public class UrlController {

private final UrlService urlService;

@Autowired
public UrlController(UrlService urlService) {
this.urlService = urlService;
}

@PostMapping
public ResponseEntity<ShortenUrlResponseDto> generateShortUrl(HttpServletRequest request) throws NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
BaseUser user = (BaseUser) request.getAttribute("user");
return ResponseEntity
.status(HttpStatus.CREATED)
.body(urlService.generateShortUrl(user));
}

@GetMapping("/{encodedId}")
public ResponseEntity<Void> redirectOriginalUrl(@PathVariable String encodedId){
return ResponseEntity
.status(HttpStatus.FOUND)
.header("Location", urlService.getOriginalUrl(encodedId))
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package JGS.CasperEvent.domain.url.dto;

public record ShortenUrlResponseDto(String shortenUrl, String shortenLocalUrl) {
}

This file was deleted.

26 changes: 26 additions & 0 deletions Server/src/main/java/JGS/CasperEvent/domain/url/entity/Url.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package JGS.CasperEvent.domain.url.entity;

import JGS.CasperEvent.global.entity.BaseEntity;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.Getter;

@Entity
@Getter
public class Url extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String originalUrl;

public Url(String originalUrl){
this.originalUrl = originalUrl;
}

public Url() {

}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
package JGS.CasperEvent.domain.url.repository;

public interface UrlRepository {
import JGS.CasperEvent.domain.url.entity.Url;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.Optional;

@Repository
public interface UrlRepository extends JpaRepository<Url, Long> {
Optional<Url> findByOriginalUrl(String originalUrl);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,71 @@
package JGS.CasperEvent.domain.url.service;

import JGS.CasperEvent.domain.url.dto.ShortenUrlResponseDto;
import JGS.CasperEvent.domain.url.entity.Url;
import JGS.CasperEvent.domain.url.repository.UrlRepository;
import JGS.CasperEvent.global.entity.BaseUser;
import JGS.CasperEvent.global.util.AESUtils;
import JGS.CasperEvent.global.util.Base62Utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.NoSuchElementException;

@Service
public class UrlService {

@Value("${client.url}")
private String clientUrl;
@Value("${client.localUrl}")
private String localClientUrl;

@Value("${shortenUrlService.url}")
private String shortenBaseUrl;


private final UrlRepository urlRepository;
private final SecretKey secretKey;

@Autowired
public UrlService(UrlRepository urlRepository, SecretKey secretKey) {
this.urlRepository = urlRepository;
this.secretKey = secretKey;
}

//todo: 테스트 끝나면 수정필요
public ShortenUrlResponseDto generateShortUrl(BaseUser user) throws NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
String encryptedUserId = AESUtils.encrypt(user.getId(), secretKey);

String originalUrl = clientUrl + "?" + "referralId=" + encryptedUserId;
String originalLocalUrl = localClientUrl + "?" + "referralId=" + encryptedUserId;

Url url = urlRepository.findByOriginalUrl(originalUrl).orElseGet(
() -> urlRepository.save(new Url(originalUrl))
);
Url localUrl = urlRepository.findByOriginalUrl(originalLocalUrl).orElseGet(
() -> urlRepository.save(new Url(originalLocalUrl))
);

Long urlId = url.getId();
Long localUrlId = localUrl.getId();

String shortenUrl = shortenBaseUrl + "/link/" + Base62Utils.encode(urlId);
String shortenLocalUrl = shortenBaseUrl + "/link/" + Base62Utils.encode(localUrlId);

return new ShortenUrlResponseDto(shortenUrl, shortenLocalUrl);
}

public String getOriginalUrl(String encodedId){
Long urlId = Base62Utils.decode(encodedId);
Url url = urlRepository.findById(urlId).orElseThrow(NoSuchElementException::new);
return url.getOriginalUrl();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package JGS.CasperEvent.global.config;

import JGS.CasperEvent.global.util.AESUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.crypto.SecretKey;

@Configuration
public class SecurityConfig {

@Value("${spring.encryption.key}")
private String encryptionKey;

@Bean
public SecretKey secretKey() {
return AESUtils.stringToKey(encryptionKey);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ public class GlobalExceptionHandler {
@ExceptionHandler(CustomException.class)
public ResponseEntity<ErrorResponse> handler(CustomException e){
return ResponseEntity
.status(HttpStatus.BAD_REQUEST)
.body(ErrorResponse.of(CustomErrorCode.BAD_REQUEST, e.getMessage()));
.status(HttpStatus.valueOf(e.getErrorCode().getStatus()))
.body(ErrorResponse.of(e.getErrorCode(), e.getMessage()));
}

@ExceptionHandler(MissingRequestCookieException.class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public class JwtAuthorizationFilter implements Filter {
"/event/rush", "/event/lottery/caspers",
"/admin/join", "/admin/auth", "/h2", "/h2/*",
"/swagger-ui/*", "/v3/api-docs", "/v3/api-docs/*",
"/event/lottery"
"/event/lottery", "/link/*"
};
private final String[] blackListUris = new String[]{
"/event/rush/*", "/event/lottery/casperBot"
Expand Down
31 changes: 31 additions & 0 deletions Server/src/main/java/JGS/CasperEvent/global/util/AESUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package JGS.CasperEvent.global.util;

import javax.crypto.*;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;

public class AESUtils {

public static SecretKey stringToKey(String keyString) {
byte[] decodedKey = keyString.getBytes();
return new SecretKeySpec(decodedKey, 0, decodedKey.length, "AES");
}

public static String encrypt(String plainText, SecretKey key) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] encryptedBytes = cipher.doFinal(plainText.getBytes());
return Base64.getEncoder().encodeToString(encryptedBytes);
}

public static String decrypt(String encryptedText, SecretKey key) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] decryptedBytes = cipher.doFinal(Base64.getDecoder().decode(encryptedText));
return new String(decryptedBytes);
}


}
25 changes: 25 additions & 0 deletions Server/src/main/java/JGS/CasperEvent/global/util/Base62Utils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package JGS.CasperEvent.global.util;

public class Base62Utils {
private static final String BASE62_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";

public static String encode(long number) {
if (number == 0) return Character.toString(BASE62_CHARS.charAt(0));

StringBuilder sb = new StringBuilder();
while (number > 0) {
int reminder = (int) (number % 62);
sb.append(BASE62_CHARS.charAt(reminder));
number /= 62;
}
return sb.reverse().toString();
}

public static long decode(String str){
long result = 0;
for (int i = 0; i < str.length(); i++) {
result = result * 62 + BASE62_CHARS.indexOf(str.charAt(i));
}
return result;
}
}
80 changes: 0 additions & 80 deletions Server/src/main/java/JGS/CasperEvent/global/util/GsonUtil.java

This file was deleted.

11 changes: 0 additions & 11 deletions Server/src/main/java/JGS/CasperEvent/global/util/UserUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,6 @@
public class UserUtil {
//TODO: 스프링 서버 뻗으면 캐스퍼 아이디 0부터 다시 시작함
private static final AtomicLong counter = new AtomicLong(0);

//TODO: 현재는 그냥 userData 즉시 반환, 키 이용한 복호화로 수정하기
public static String getDecodedPhoneNumber(String userData) {
return userData;
}

//TODO: 현재는 true 리턴, jwt로 변경 필요
public static Boolean isValidAdminToken(String token){
if (token.equals("adminToken")) return true;
return false;
}
public static long generateId(){
return counter.incrementAndGet();
}
Expand Down
Loading

0 comments on commit 8871ce3

Please sign in to comment.