Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update README.md #123

Open
wants to merge 3 commits into
base: weekly_11
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,82 @@
# Team14_BE

14조 백엔드

## 프로젝트 소개
- **프로젝트 명**: 요기먹때
- **프로젝트 기간**: 24.09 ~ 24.11
- **프로젝트 목표**: 높아진 배달 요구 금액에 대한 부담을 모르는 사람과 함께 주문하여 해결한다.

> 모르는 사람과 함께 배달을 시켜보세요!

![image](https://github.com/user-attachments/assets/0d40077d-5791-4a3d-981d-8c2726c2083f)

- 🙋🏻‍♀️배달 가격이 부담스러운 사람을 대상으로
- 👩‍👦함께 배달 음식을 주문할 수 있는 사람을 매칭해
- 💵배달 최소 주문 금액 부담을 덜고 추가적인 배달음식비 또는 배달팁을 줄여보세요!

## 배포 링크

✅FE: https://team14-fe.vercel.app/
✅BE: https://order-together.duckdns.org/api/v1

## 주요기능

### 1. 주문 결제(토스 페이먼츠 API)
* 토스페이먼츠 API 를 사용한 포인트 충전 시스템
* 요기먹때에서 사용되는 재화를 충전할 수 있습니다.


## ERD 이미지

![image](https://github.com/user-attachments/assets/59b8b750-ceb3-4484-a538-acd53d7fc370)

## 개발 인원 : 7명

| 이름 | 담당 역할 및 기능 |
| ------ | ---------------------------------------------------------------------- |
| 강호정 | <img src="https://img.shields.io/badge/-FE-blue"> 마이페이지 |
| 서민지 | <img src="https://img.shields.io/badge/-FE-blue"> 스팟(메인)페이지 |
| 임지환 | <img src="https://img.shields.io/badge/-FE-blue"> 로그인 및 결제페이지 |
| 나제법 | <img src="https://img.shields.io/badge/-BE-red"> 결제 API |
| 서영우 | <img src="https://img.shields.io/badge/-BE-red"> 로그인 API, 회원 API |
| 안재영 | <img src="https://img.shields.io/badge/-BE-red"> SMS API, 결제내역 API |
| 유보민 | <img src="https://img.shields.io/badge/-BE-red"> 지도(스팟) API |

## ⚒️기술 스택
### Language
* Java 21
### Framework
* Spring Boot 3.3.3
* Spring Data JPA
### Database
* MySQL 8.0
### Infra
* Amazon Web Service
### Testing
* JUnit5
* Mockito

## 💻UI
<details>
<summary><h4>로그인 UI</h4></summary>

![image](https://github.com/user-attachments/assets/21048e34-2189-4c7c-b5a7-a08a72d794fa)
![image](https://github.com/user-attachments/assets/c01f8c58-8d28-4794-9fd0-562888dc5abf)

</details>

<details>
<summary><h4>근처 함께 주문 건 확인</h4></summary>

![image](https://github.com/user-attachments/assets/fc30c84e-6976-4aa9-a01b-41139462df23)

</details>

<details>
<summary><h4>함께 주문하기 생성</h4></summary>

![image](https://github.com/user-attachments/assets/c0f54d1b-c3bd-4a89-a938-087fd83d4e63)
![image](https://github.com/user-attachments/assets/5d86ad01-ba0f-4050-ac64-b367dd45528b)

</details>
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ dependencies {
implementation "io.jsonwebtoken:jjwt-api:${jjwt_version}"
implementation "org.springdoc:springdoc-openapi-starter-webmvc-ui:${swagger_version}"
implementation 'org.mapstruct:mapstruct:1.6.2'
implementation 'ch.hsr:geohash:1.4.0'

annotationProcessor 'org.mapstruct:mapstruct-processor:1.6.2'

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class JwtUtil {
private final SecretKey key;
private final int expireTime;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,56 +1,29 @@
package com.ordertogether.team14_be.auth.application.service;

import com.ordertogether.team14_be.auth.JwtUtil;
import com.ordertogether.team14_be.auth.application.dto.KakaoUserInfo;
import com.ordertogether.team14_be.auth.presentation.KakaoClient;
import com.ordertogether.team14_be.common.web.response.ApiResponse;
import com.ordertogether.team14_be.member.application.service.MemberService;
import com.ordertogether.team14_be.member.persistence.entity.Member;
import java.net.URI;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;

@RequiredArgsConstructor
@Service
public class AuthService {

private final KakaoClient kakaoClient;
private final MemberService memberService;
private final JwtUtil jwtUtil;

@Value(("${FRONT_PAGE_SIGNUP}"))
String redirectPage;

public ResponseEntity<ApiResponse<String>> kakaoLogin(String authorizationCode) {
String kakaoToken = kakaoClient.getAccessToken(authorizationCode); // 인가코드로부터 카카오토큰 발급
KakaoUserInfo kakaoUserInfo = kakaoClient.getUserInfo((kakaoToken));
String userKakaoEmail = kakaoUserInfo.kakaoAccount().email(); // 와 사용자 카카오 이메일이야

Optional<Member> existMember = memberService.findMemberByEmail(userKakaoEmail);
if (existMember.isPresent()) {
String serviceToken =
jwtUtil.generateToken(memberService.getMemberId(userKakaoEmail)); // 서비스 토큰 줘야징
return ResponseEntity.ok((ApiResponse.with(HttpStatus.OK, "로그인 성공", serviceToken)));
} else {
return ResponseEntity.status(HttpStatus.FOUND)
.location(
URI.create(redirectPage + URLEncoder.encode(userKakaoEmail, StandardCharsets.UTF_8)))
.build();
}
public AuthService(MemberService memberService, JwtUtil jwtUtil) {
this.memberService = memberService;
this.jwtUtil = jwtUtil;
}

public ResponseEntity<ApiResponse<String>> register(
String email, String deliveryName, String phoneNumber) {
public String register(String email, String deliveryName, String phoneNumber) {
Member member = new Member(email, deliveryName, phoneNumber);
memberService.registerMember(member);
Long memberId = memberService.getMemberId(email);
String serviceToken = jwtUtil.generateToken(memberId);
return ResponseEntity.ok((ApiResponse.with(HttpStatus.OK, "회원가입 및 로그인 성공", serviceToken)));
return serviceToken;
}

public String getServiceToken(String email) {
return jwtUtil.generateToken(memberService.getMemberId(email));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.ordertogether.team14_be.auth.application.service;

import com.ordertogether.team14_be.auth.application.dto.KakaoUserInfo;
import com.ordertogether.team14_be.auth.presentation.KakaoClient;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@RequiredArgsConstructor
@Service
public class KakaoAuthService {
private final KakaoClient kakaoClient;

public String getKakaoUserEmail(String authorizationCode) {
String kakaoToken = kakaoClient.getAccessToken(authorizationCode);
KakaoUserInfo kakaoUserInfo = kakaoClient.getUserInfo((kakaoToken));
String userKakaoEmail = kakaoUserInfo.kakaoAccount().email();
return userKakaoEmail;
}
}
Original file line number Diff line number Diff line change
@@ -1,34 +1,119 @@
package com.ordertogether.team14_be.auth.presentation;

import com.ordertogether.team14_be.auth.application.service.AuthService;
import com.ordertogether.team14_be.auth.application.service.KakaoAuthService;
import com.ordertogether.team14_be.common.web.response.ApiResponse;
import com.ordertogether.team14_be.member.application.dto.MemberInfoRequest;
import lombok.RequiredArgsConstructor;
import com.ordertogether.team14_be.member.application.service.MemberService;
import com.ordertogether.team14_be.member.persistence.entity.Member;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseCookie;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RequiredArgsConstructor
@RestController
@Controller
@RequestMapping("/api/v1/auth")
public class AuthController {

private final AuthService authService;
private final KakaoAuthService kakaoAuthService;
private final String redirectPage;
private final MemberService memberService;

public AuthController(
AuthService authService,
KakaoAuthService kakaoAuthService,
MemberService memberService,
@Value("${FRONT_PAGE_SIGNUP}") String redirectPage) {
this.authService = authService;
this.kakaoAuthService = kakaoAuthService;
this.memberService = memberService;
this.redirectPage = redirectPage;
}

@GetMapping("/login")
public ResponseEntity<ApiResponse<String>> getToken(@RequestHeader String authorizationCode) {
return authService.kakaoLogin(authorizationCode);
public ResponseEntity<ApiResponse<String>> getToken(
@RequestHeader("Authorization") String authorizationHeader,
HttpServletResponse httpServletResponse) {
String authorizationCode = authorizationHeader.replace("Bearer ", "");
System.out.println("인가코드:" + authorizationCode);
String userKakaoEmail = kakaoAuthService.getKakaoUserEmail(authorizationCode);
System.out.println("이메일:" + userKakaoEmail);
Optional<Member> existMember = memberService.findMemberByEmail(userKakaoEmail);
if (existMember.isPresent()) {
String serviceToken = authService.getServiceToken(userKakaoEmail);

ResponseCookie cookie =
ResponseCookie.from("serviceToken", serviceToken)
.httpOnly(true)
.secure(true)
.path("/")
.sameSite("Strict")
.build();

HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.SET_COOKIE, cookie.toString());

return ResponseEntity.ok()
.headers(headers)
.body(ApiResponse.with(HttpStatus.OK, "로그인 성공", serviceToken));
} else {
String redirectUrl = redirectPage + userKakaoEmail;
try {
httpServletResponse.sendRedirect(redirectUrl);
} catch (IOException e) {
System.out.println(e.getMessage());
}
return ResponseEntity.ok().body(ApiResponse.with(HttpStatus.OK, "리다이렉트", redirectUrl));
}
}

@PostMapping("/signup")
public ResponseEntity<ApiResponse<String>> signUpMember(
@RequestParam String email, @RequestBody MemberInfoRequest memberInfoRequest) {
return authService.register(
email, memberInfoRequest.deliveryName(), memberInfoRequest.phoneNumber());
String serviceToken =
authService.register(
email, memberInfoRequest.deliveryName(), memberInfoRequest.phoneNumber());

ResponseCookie cookie =
ResponseCookie.from("serviceToken", serviceToken)
.httpOnly(true)
.secure(true)
.path("/")
.sameSite("Strict")
.build();

HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.SET_COOKIE, cookie.toString());

return ResponseEntity.ok()
.headers(headers)
.body(ApiResponse.with(HttpStatus.OK, "회원가입 성공", serviceToken));
}

@PostMapping("/logout")
public void logout(HttpServletResponse response) {
ResponseCookie deleteCookie =
ResponseCookie.from("serviceToken", "")
.maxAge(0)
.httpOnly(true)
.secure(true)
.path("/")
.sameSite("Strict")
.build();

HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.SET_COOKIE, deleteCookie.toString());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,15 @@
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class ApiResponse<T> {

private Integer status;
private String message;
private T data;
private final Integer status;
private final String message;
private final T data;

public static <T> ApiResponse<T> with(HttpStatus httpStatus, String message, @Nullable T data) {
return new ApiResponse<>(httpStatus.value(), message, data);
}

public static <T> ApiResponse<T> with(HttpStatus httpStatus, String message) {
return new ApiResponse<>(httpStatus.value(), message, null);
}
}
19 changes: 19 additions & 0 deletions src/main/java/com/ordertogether/team14_be/config/CorsConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.ordertogether.team14_be.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry
.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowedHeaders("Authorization", "Content-Type")
.exposedHeaders("Custom-Header")
.maxAge(3600);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,12 @@ public AuditorAware<Long> auditorProvider() {

@Bean
public PaymentEventRepository paymentEventRepository(
SimpleJpaPaymentEventRepository simpleJpaPaymentEventRepository) {
return new JpaPaymentEventRepository(simpleJpaPaymentEventRepository);
SimpleJpaPaymentEventRepository simpleJpaPaymentEventRepository,
SimpleJpaPaymentOrderRepository simpleJpaPaymentOrderRepository,
SimpleJpaProductRepository simpleJpaProductRepository) {
return new JpaPaymentEventRepository(
simpleJpaPaymentEventRepository,
paymentOrderRepository(simpleJpaPaymentOrderRepository, simpleJpaProductRepository));
}

@Bean
Expand Down
Loading