API (4) - Url Shortener + 42Ouath API
안녕하세요. 이번에는 Shortner과 42Ouath를 합쳐서 Url을 관리하는 API를 만들어봤습니다.
물론 2개는 이미 만들어봐서 합치면 되는 프로젝트였습니다.
최종적으로 1달 정도 프로젝트를 진행한 것을 정리해보겠습니다.
주요 기능
※ 42로그인 했을 경우 42계정에 맞는 URL을 서버에서 관리해줍니다.
추가적으로 URL접근 횟수도 기록해주는 기능을 추가했습니다.
※ 로그인 하지 않을 경우는 추가적인 기능은 제공하지 않지만 여러 URL을 생성할 수 있습니다.
DB TABLE구성
ID | HashValue | OriginUrl | Name | Count |
BigInteger | String | String | String | Integer |
Project구성
URL을 맵핑해주는 controller
JPA Entity역할을 하는 domain
Data를 이동하기 위한 객체들인 dto
서버에서 발생하는 error들을 전체적으로 컨트롤해주는 error
DB에 접근하여 Data를 가져오는 repository
핵심적인 비지니스 로직이 들어있는 service
URL변경에 필요한 알고리즘과 Dto를 생성하는 부분이 들어있는 utils
전체 Bean을 관리해 의존성 주입을 담담하는 Config파일로 구성되어있습니다.
간략한 설명은 아래와 같습니다.
1. Config
아래의 코드와 같이 Bean들을 등록해서 의존성 주입을 통해 직접 필요한 곳에 넣어줍니다.
이렇게 함으로써 싱글톤을 유지할 수 있습니다.
@Configuration
public class Config {
private UrlRepository urlRepository;
@Autowired
public Config(UrlRepository urlRepository) {
this.urlRepository = urlRepository;
}
@Bean
public UrlService urlService() {
return new UrlServiceImpl(urlRepository,urlCheckService(),makeDto(),base62Converter());
}
@Bean
public UrlCheckService urlCheckService() {
return new UrlCheckService(makeDto());
}
@Bean
public OauthService oauthService() {
return new OauthServiceImpl(urlRepository,objectMapper(),base62Converter());
}
@Bean
public ObjectMapper objectMapper() {
return new ObjectMapper();
}
@Bean
public Base62Converter base62Converter() {
return new Base62Converter();
}
@Bean
public MakeDto makeDto() {
return new MakeDto();
}
}
2. Controller
크게 4가지로 나누어져있습니다.
첫 번째는 인코딩된 62Base 7글자 입력 할 경우 OriginUrl로 Redirect시켜주는 부분
두 번째는 로그인 하지 않았을 경우 Shorten Url을 만들어주는 부분
세 번째는 로그인 했을 경우 Shorten Url을 만들어주는 부분
마지막 부분은 42Oauth의 Access_Token을 만들어 API정보를 가져오는 역할로 구성되어있습니다.
@Slf4j
@Controller
@CrossOrigin("*")
public class UrlController {
private UrlService urlService;
private OauthService oauthService;
private MakeDto makeDto;
private ObjectMapper objectMapper;
TokenDto tokenDto = new TokenDto();
@Autowired
public UrlController(UrlService urlService, OauthService oauthService, MakeDto makeDto, ObjectMapper objectMapper) {
this.urlService = urlService;
this.oauthService = oauthService;
this.makeDto = makeDto;
this.objectMapper = objectMapper;
}
@GetMapping("/sa/{code}")
public void DirectUrl(@PathVariable String code, HttpServletResponse response) throws Exception {
log.info("code = {}", code);
UrlResponseDto responseDto = urlService.findByHashValue(code);
response.sendRedirect(responseDto.getOriginUrl());
}
@PostMapping("/general")
@ResponseBody
public ResultResponseDto<Object> ChangeUrl(@RequestParam(required = false) String originUrl)
throws NoSuchAlgorithmException, ValidationException{
UrlResponseDto responseDto = urlService.createUrl(originUrl);
return makeDto.makeResultResponseDto(responseDto);
}
@GetMapping("/login")
public String CodeUrl(@RequestParam String code, Model model)
throws JsonProcessingException {
ResponseEntity<String> response = oauthService.makeToken(code);
tokenDto = objectMapper.readValue(response.getBody(), TokenDto.class);
NameImgModel nameImgModel = oauthService.getModel(tokenDto.getAccess_token(), model);
return "login";
}
@PostMapping("/login")
@ResponseBody
public ResultResponseDto<Object> ChangeUrlLogin(@RequestParam(required = false) String originUrl,
@RequestParam(required = true) String name)
throws ValidationException, UnsupportedEncodingException, NoSuchAlgorithmException {
UrlResponseDto responseDto = urlService.createUrlWithLogin(originUrl,name);
return makeDto.makeResultResponseDto(responseDto);
}
}
3. UrlService
서비스의 핵심 로직만 설명드리겠습니다.
originUrl이 유효한지 확인합니다. 그리고 유효하다면 10개의 16진수를 추출합니다.
이것으로 Url객체를 만들어 DB에 접근하여 동일한 정보들이 있는지 확인합니다.
로그인 안했을 경우에는 duplicateNameAndOriginUrl부분이 필요없습니다.
10개의 16진수로 62Base문자를 만들어 클라이언트로 보내줍니다.
자세한 코드는 Github주소로 참고바랍니다.
public UrlResponseDto createUrlWithLogin(String originUrl, String name) throws NoSuchAlgorithmException, ValidationException, UnsupportedEncodingException {
originUrl = urlCheckService.checkUrl(originUrl);
String extract10Char = makeExtract10Char(originUrl);
Url url = new Url(extract10Char,originUrl,name);
duplicate10CharCheck(extract10Char);
duplicateNameAndOriginUrl(originUrl, name);
urlRepository.save(url);
String encodedStr = base62Converter.encoding(extract10Char);
return makeDto.makeUrlResponseDto(originUrl, extract10Char, encodedStr,name);
}
4. OauthService
Oauth를 이용해 필요한 정보는 사진과 이름 정보입니다. 이를 이용해 Url을 매칭 시킬예정입니다.
자세한 Oauth내용은 이 글을 참고해주시기 바랍니다.
5. Repository
Repository에서는 아래와 같은 메서드를 이용합니다.
SpringJpa는 아래와 같이 쓰지 않아도 알아서 find나 exist같은 경우는 만들어줍니다.
그러나 보기 쉽게하도록 나열했습니다. 아래의 두 개 메소드는 sqld구문을 이용하여 작성했습니다.
public interface UrlRepository extends JpaRepository<Url, Long> {
Optional<Url> findByhashvalue(String hashvalue);
boolean existsByhashvalue(String hashvalue);
boolean existsByname(String name);
boolean existsByoriginurl(String originurl);
@Query("SELECT m FROM Url m WHERE m.name= :name and m.originurl= :originurl")
Optional<Url> findUrl(@Param("name") String name,@Param("originurl") String originurl);
@Query("SELECT m FROM Url m WHERE m.name= :name")
List<Url> findAllUrlByName(@Param("name") String name);
}
결과
<42로그인을 했을 경우>
<42로그인을 하지 않았을 경우>
느낀 점
이번 프로젝트를 진행하면서 많이 부족하고 기초적인 지식이 없다는 것을 새삼느꼈습니다.
그래서 중간에 그냥 이 정도만 할까?라는 생각이 계속 들었던 것 같습니다.
물론 현재에도 손봐야 될 부분이 많이 남아있습니다. 이것은 공부를 하면서 중간중간 보완해나갈 예정입니다.
프로젝트를 마무리하고 나서 돌아보니 많이 성장해있었습니다.
하루하루로보면 조금이라도 모아놓고 1달로보니 많이 배운 것 같습니다.
이 다음으로는 오픈소스프로젝트에 도전하려고합니다.
42Helper 시스템의 기능을 개선하고 버그를 찾아 다른 사람들과 협력하는 방법을 익히고 싶습니다.
다음엔 더 성장한 모습으로 돌아오겠습니다.