diff --git "a/keyword/Chapter09/\355\202\244\354\233\214\353\223\234\354\240\225\353\246\254.md" "b/keyword/Chapter09/\355\202\244\354\233\214\353\223\234\354\240\225\353\246\254.md" new file mode 100644 index 0000000..60a90e5 --- /dev/null +++ "b/keyword/Chapter09/\355\202\244\354\233\214\353\223\234\354\240\225\353\246\254.md" @@ -0,0 +1,119 @@ +- 서버 + - 서버란 무엇이고, 어떤 역할을 할까요? + + 서버(Server)는 클라이언트로부터 요청(Request)을 받아 이를 처리한 후 응답(Response)을 반환하는 컴퓨터 또는 프로그램입니다. 주로 데이터 저장, 처리, 공유 등의 역할을 합니다. + + - 현실에서 서버와 유사한 역할을 하는 예시는 무엇이 있을까요? + - 도서관 사서 + + 우리가 책(데이터)를 요청하면 사서(서버)는 책을 찾아서 전달해준다. + + - 쿠팡 + + 우라 물건(데이터)를 주문하면 쿠팡은 물건을 전해준다. + +- 서버와 클라이언트 + - 서버와 클라이언트는 일반적으로 1:1, 1:N, N:M 관계 중 어떤 관계를 가지고 있을까요? + - 위의 문장의 답을 찾고, 아래 문장을 완성해보세요. + - 서버는 다수의 클라이언트가 접근한다. + - 즉 서버와 클라이언트는 일반적으로 1:N의 관계다. + - 서버와 클라이언트의 통신 구조를 식당 예시와 연결하여 정리하고 스터디에서 본인이 정리한 예시를 공유해 보세요. + (식당에는 손님, 점원, 주방장, 냉장고가 있습니다.) + - 손님: 클라이언트 (요청자) + - 점원: API 서버 (중간에서 요청을 전달하고 응답을 다시 전달함) + - 주방장: 데이터베이스 서버 또는 내부 로직 처리자 + - 냉장고: 저장된 데이터 (데이터베이스) + + 식당에서 손님이 점원에게 음식을 요청하면, 점원은 주방장에게 전달하고 요리가 완성되면 손님에게 전달합니다. 이와 유사하게 클라이언트는 서버에 요청을 보내고, 서버는 요청을 처리한 후 응답을 보냅니다. + +- 서버와 클라이언트 간의 통신 + - 서버와 클라이언트가 정보를 주고 받으려면 먼저 어떤 일을 해야할까요? + - 정보를 주고받기 위해서는 먼저 **서버와 클라이언트가 연결을 설정**해야 하며, 주로 URL과 포트를 기반으로 접속합니다. + - API 서버와 소통하기 위해 필요한 정보들을 담고있는 문서는 무엇일까요? + (Hint: ‘API’라는 단어와 ‘문서’라는 단어를 함께 검색해보세요!) + + API 문서(API Documentation) + +- 서버와 클라이언트 간의 통신 방식 - HTTP Protocol + - HTTP Protocol에서 정보를 주고받기 위한 중요한 구성요소 2가지는 무엇일까요? + - Request + + 클라이언트가 서버에 보내는 메시지로, 요청 방식(method), URL, 헤더(header), 본문(body) 등으로 구성됨 + + - Response + + 서버가 클라이언트의 요청에 대해 응답하는 메시지로, 상태 코드(status code), 헤더(header), 본문(body) 등을 포함 + + + - HTTP 통신은 연결을 계속 유지하고 있을까요? 유지한다면 어떻게 유지할 수 있는지, 유지할 수 없다면 왜 유지할 수 없는지 찾아보세요. (Hint: State) + + **HTTP 통신은 기본적으로 연결을 유지하지 않는다. (Stateless)** + + 서버는 클라이언트의 이전 요청 상태를 저장하지 않으며, 각 요청은 독립적으로 처리됩니다. 따라서 기본적으로 연결을 유지하지 않으며 상태 정보를 저장하려면 별도의 기술이 필요합니다. 연결 유지가 필요한 경우는 다음과 같이 해결할 수 있습니다. + + - 쿠키(cookie)와 세션(session): 클라이언트 상태를 서버 또는 클라이언트에 저장 + - 토큰 기반 인증(JWT 등): 매 요청마다 토큰을 헤더에 포함하여 상태 유지 대체 + - HTTP persistent connection (Keep-Alive): 일정 시간 동안 동일 연결을 재사용 + - HTTP Protocol에서는 Method를 통해 어떤 동작을 하고싶은지 나타낼 수 있습니다. + 주요 Method 5가지는 무엇이 있을까요? + - GET: 리소스를 조회하기 위한 요청. 서버의 상태나 데이터를 변경하지 않음 + - POST: 서버에 데이터를 생성(등록) 요청 + - PUT: 리소스를 전체 수정 + - DELETE: 리소스를 삭제 + - PATCH: 리소스를 부분 수정 + - 데이터를 주고받을 때 주로 사용하는 방식에 대해 알아보세요. + - Query Parameter (또는 Query String): URL에 `?key=value` 형식으로 붙는 데이터. + + ex) `/users?name=John` + + - Path Variable: URL 경로의 일부로서 식별자 등을 포함. 예: /users/123 + - Body: 요청의 본문에 포함되는 데이터로, POST, PUT, PATCH 요청 시 주로 사용 + - Header: 요청 또는 응답의 부가 정보. 인증 정보(Authorization), 콘텐츠 타입(Content-Type) 등이 포함됨 + - HTTP에서 데이터를 주고받을 때는 서로 이해할 수 있는 특정 형식으로 진행해야 합니다. + 아래의 형식들에 대해 조사해보세요. + - JSON(JavaScript Object Notation) + + 텍스트 기반의 경량 데이터 교환 형식으로 키-값 쌍으로 구성되어 사람이 읽기 쉬움 + + - XML(eXtensible Markup Language) + + 태그 기반의 데이터 표현 방식으로, 데이터 구조가 명확하지만 무겁고 복잡할 수 있음 + + - HTTPS란 무엇이고, HTTP와 어떤 차이가 있을까요? + + **HTTP에 보안 계층(SSL/TLS)을 추가한 프로토콜** + + . HTTP는 데이터를 암호화하지 않지만, HTTPS는 데이터를 암호화하여 네트워크 상에서 제3자에 의해 읽히거나 변조되지 않도록 보호합니다. 이 때문에 금융, 로그인 등 민감한 정보를 다루는 웹사이트는 HTTPS를 사용해야 합니다. + + + +- API + - API란 무엇일까요? (Restful API가 무엇인지도 함께 정리해 주세요.) + - API(Application Programming Interface) + + 소프트웨어 간 상호작용을 위한 인터페이스로 ****외부 프로그램이 특정 기능을 사용할 수 있도록 함수, 명세 등을 제공한다. + + - Restful API + + HTTP 기반의 API 설계 원칙 중 하나로 자원의 URI를 통해 요청을 주고받으며, 상태(State)를 서버에 저장하지 않는다. + + +- Android에서 HTTP 통신하기 + - Android에서 HTTP 통신을 위해서는 외부 라이브러리를 사용해야 편리하게 진행할 수 있습니다. + 그 중 대표적인 라이브러리 Retrofit2에 대해서 찾아보세요. + + Retrofit2는 Android에서 HTTP 통신을 쉽게 할 수 있도록 도와주는 라이브러리다. 인터페이스 기반으로 API 요청을 정의하며, Gson 등의 변환기를 통해 데이터를 객체로 쉽게 처리할 수 있다. + + - Retrofit2를 실제로 Android App 내에서 사용하기 위해서 만들어야 하는 요소 3가지는 무엇이 있을까요? + - API 인터페이스 파일 (Service) + - 서버와 통신할 메서드를 정의하는 인터페이스 + - 예: @GET("/users"), @POST("/login") 등 + - Retrofit 객체 생성 + - baseUrl, converter(GsonConverterFactory 등), OkHttpClient 등을 설정하여 인스턴스를 생성 + - 데이터 모델 클래스 + - 서버에서 주고받는 JSON 데이터를 Kotlin/Java 객체로 변환하기 위한 클래스 \ No newline at end of file diff --git a/mission/Flo/app/src/main/java/com/example/flo/NetWork/AuthResponse.kt b/mission/Flo/app/src/main/java/com/example/flo/NetWork/AuthResponse.kt new file mode 100644 index 0000000..39d565c --- /dev/null +++ b/mission/Flo/app/src/main/java/com/example/flo/NetWork/AuthResponse.kt @@ -0,0 +1,13 @@ +package com.example.flo.NetWork + +import com.google.gson.annotations.SerializedName + +data class AuthResponse( + @SerializedName("isSuccess") val isSuccess: Boolean, + @SerializedName("code") val code: Int, + @SerializedName("message") val message: String, + @SerializedName("result") val result: Result? + +) + + diff --git a/mission/Flo/app/src/main/java/com/example/flo/NetWork/AuthRetrofitInterface.kt b/mission/Flo/app/src/main/java/com/example/flo/NetWork/AuthRetrofitInterface.kt new file mode 100644 index 0000000..8f7be35 --- /dev/null +++ b/mission/Flo/app/src/main/java/com/example/flo/NetWork/AuthRetrofitInterface.kt @@ -0,0 +1,15 @@ +package com.example.flo.NetWork + +import com.example.flo.data.User +import retrofit2.Call +import retrofit2.http.Body +import retrofit2.http.POST + +interface AuthRetrofitInterface { + @POST("/users") + fun signUp(@Body user: User): Call + + @POST("/users/login") + fun login(@Body user: User): Call + +} \ No newline at end of file diff --git a/mission/Flo/app/src/main/java/com/example/flo/NetWork/AuthService.kt b/mission/Flo/app/src/main/java/com/example/flo/NetWork/AuthService.kt new file mode 100644 index 0000000..b6f1642 --- /dev/null +++ b/mission/Flo/app/src/main/java/com/example/flo/NetWork/AuthService.kt @@ -0,0 +1,71 @@ +package com.example.flo.NetWork + +import android.util.Log +import com.example.flo.data.User +import com.example.flo.ui.login.LoginView +import com.example.udemy_android_template.ui.siginup.SignUpView +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response + +class AuthService { + private lateinit var signUpView: SignUpView + private lateinit var loginView: LoginView + + fun setSignUpView(signUpView: SignUpView) { + this.signUpView = signUpView + } + + fun setLoginView(loginView: LoginView) { + this.loginView = loginView + } + + fun signUp(user: User) { + + val signUpService = getRetrofit().create(AuthRetrofitInterface::class.java) + + signUpService.signUp(user).enqueue(object : Callback { + override fun onResponse(call: Call, response: Response) { + if (response.isSuccessful && response.code() == 200) { + val signUpResponse: AuthResponse = response.body()!! + + Log.d("SIGNUP-RESPONSE", signUpResponse.toString()) + + when (val code = signUpResponse.code) { + 1000 -> signUpView.onSignUpSuccess() + 2016, 2017 -> { + signUpView.onSignUpFailure() + } + } + } + } + + override fun onFailure(call: Call, t: Throwable) { + //실패처리 + } + }) + } + + + fun login(user: User) { + val loginService = getRetrofit().create(AuthRetrofitInterface::class.java) + + + loginService.login(user).enqueue(object : Callback { + override fun onResponse(call: Call, response: Response) { + if (response.isSuccessful && response.code() == 200) { + val loginResponse: AuthResponse = response.body()!! + + when (val code = loginResponse.code) { + 1000 -> loginView.onLoginSuccess(code,loginResponse.result!! ) + else -> loginView.onLoginFailure() + } + } + } + + override fun onFailure(call: Call, t: Throwable) { + //실패처리 + } + }) + } +} \ No newline at end of file diff --git a/mission/Flo/app/src/main/java/com/example/flo/NetWork/NetworkModule.kt b/mission/Flo/app/src/main/java/com/example/flo/NetWork/NetworkModule.kt new file mode 100644 index 0000000..d636361 --- /dev/null +++ b/mission/Flo/app/src/main/java/com/example/flo/NetWork/NetworkModule.kt @@ -0,0 +1,19 @@ +package com.example.flo.NetWork + + +import okhttp3.OkHttpClient +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory +import java.util.concurrent.TimeUnit + +const val BASE_URL = "http://3.35.121.185" + +fun getRetrofit(): Retrofit { + + val retrofit = Retrofit.Builder() + .baseUrl(BASE_URL) + .addConverterFactory(GsonConverterFactory.create()) + .build() + + return retrofit +} \ No newline at end of file diff --git a/mission/Flo/app/src/main/java/com/example/flo/NetWork/Result.kt b/mission/Flo/app/src/main/java/com/example/flo/NetWork/Result.kt new file mode 100644 index 0000000..7056c92 --- /dev/null +++ b/mission/Flo/app/src/main/java/com/example/flo/NetWork/Result.kt @@ -0,0 +1,8 @@ +package com.example.flo.NetWork + +import com.google.gson.annotations.SerializedName + +data class Result( + @SerializedName("userIdx") val userIdx: Int, + @SerializedName("jwt") val jwt: String +) diff --git a/mission/Flo/app/src/main/java/com/example/flo/data/User.kt b/mission/Flo/app/src/main/java/com/example/flo/data/User.kt new file mode 100644 index 0000000..534309e --- /dev/null +++ b/mission/Flo/app/src/main/java/com/example/flo/data/User.kt @@ -0,0 +1,18 @@ +package com.example.flo.data + +import androidx.room.Entity +import androidx.room.PrimaryKey +import com.google.gson.annotations.SerializedName + +@Entity(tableName = "UserTable") +data class User( + @SerializedName(value = "email") + var email : String, + @SerializedName(value = "password") + var password : String, + @SerializedName(value = "name") + var name : String +){ +@PrimaryKey(autoGenerate = true) +val id : Int = 0 +} diff --git a/mission/Flo/app/src/main/java/com/example/flo/ui/login/LoginActivity.kt b/mission/Flo/app/src/main/java/com/example/flo/ui/login/LoginActivity.kt new file mode 100644 index 0000000..22aa6eb --- /dev/null +++ b/mission/Flo/app/src/main/java/com/example/flo/ui/login/LoginActivity.kt @@ -0,0 +1,94 @@ +package com.example.udemy_android_template.ui.login + +import android.content.Intent +import android.os.Bundle +import android.widget.Toast +import androidx.appcompat.app.AppCompatActivity +import com.example.flo.MainActivity +import com.example.flo.NetWork.AuthService +import com.example.flo.NetWork.Result +import com.example.flo.ui.login.LoginView +import com.example.flo.data.User +import com.example.flo.databinding.ActivityLoginBinding +import com.example.udemy_android_template.ui.siginup.SignUpActivity + + +class LoginActivity : AppCompatActivity(), LoginView { + lateinit var binding: ActivityLoginBinding + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityLoginBinding.inflate(layoutInflater) + setContentView(binding.root) + + binding.loginSignUpTv.setOnClickListener { + startActivity(Intent(this, SignUpActivity::class.java)) + } + + binding.loginSignInBtn.setOnClickListener { + login() + } + } + + + + private fun login() { + if (binding.loginIdEt.text.toString().isEmpty() || binding.loginDirectInputEt.text.toString().isEmpty()) { + Toast.makeText(this, "이메일을 입력해주세요.", Toast.LENGTH_SHORT).show() + return + } + + if (binding.loginPasswordEt.text.toString().isEmpty()) { + Toast.makeText(this, "비밀번호를 입력해주세요.", Toast.LENGTH_SHORT).show() + return + } + + val authService = AuthService() + authService.setLoginView(this) + + authService.login(getUser()) + } + + private fun getUser(): User { + val email = binding.loginIdEt.text.toString() + "@" + binding.loginDirectInputEt.text.toString() + val password = binding.loginPasswordEt.text.toString() + + return User(email = email, password = password, name = "") + } + + private fun startMainActivity() { + val intent = Intent(this, MainActivity::class.java) + startActivity(intent) + } + + private fun saveJwt(jwt: Int) { + val spf = getSharedPreferences("auth" , MODE_PRIVATE) + val editor = spf.edit() + + editor.putInt("jwt", jwt) + editor.apply() + } + + private fun saveJwt2(jwt: String) { + val spf = getSharedPreferences("auth2" , MODE_PRIVATE) + val editor = spf.edit() + + editor.putString("jwt", jwt) + editor.apply() + } + + + + override fun onLoginSuccess(code : Int, result : Result) { + when(code) { + 1000 -> { + saveJwt2(result.jwt) + startMainActivity() + + } + } + } + + override fun onLoginFailure() { + } +} \ No newline at end of file diff --git a/mission/Flo/app/src/main/java/com/example/flo/ui/login/LoginView.kt b/mission/Flo/app/src/main/java/com/example/flo/ui/login/LoginView.kt new file mode 100644 index 0000000..4cf7a93 --- /dev/null +++ b/mission/Flo/app/src/main/java/com/example/flo/ui/login/LoginView.kt @@ -0,0 +1,8 @@ +package com.example.flo.ui.login + + +import com.example.flo.NetWork.Result +interface LoginView { + fun onLoginSuccess(code : Int, result : Result) + fun onLoginFailure() +} \ No newline at end of file diff --git a/mission/Flo/app/src/main/java/com/example/flo/ui/notifications/SearchFragment.kt b/mission/Flo/app/src/main/java/com/example/flo/ui/notifications/SearchFragment.kt new file mode 100644 index 0000000..e5f03e7 --- /dev/null +++ b/mission/Flo/app/src/main/java/com/example/flo/ui/notifications/SearchFragment.kt @@ -0,0 +1,29 @@ +package com.example.flo.ui.notifications + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import com.example.flo.databinding.FragmentSearchBinding + +class SearchFragment : Fragment() { + + private var _binding: FragmentSearchBinding? = null + private val binding get() = _binding!! + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = FragmentSearchBinding.inflate(inflater, container, false) + + return binding.root + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } +} \ No newline at end of file diff --git a/mission/Flo/app/src/main/java/com/example/flo/ui/siginup/SignUpActivity.kt b/mission/Flo/app/src/main/java/com/example/flo/ui/siginup/SignUpActivity.kt new file mode 100644 index 0000000..8c66f3f --- /dev/null +++ b/mission/Flo/app/src/main/java/com/example/flo/ui/siginup/SignUpActivity.kt @@ -0,0 +1,70 @@ +package com.example.udemy_android_template.ui.siginup + +import android.os.Bundle +import android.util.Log + +import android.widget.Toast +import androidx.appcompat.app.AppCompatActivity +import com.example.flo.NetWork.AuthService +import com.example.flo.data.User +import com.example.flo.databinding.ActivitySignupBinding + + +class SignUpActivity : AppCompatActivity() , SignUpView{ + + lateinit var binding: ActivitySignupBinding + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivitySignupBinding.inflate(layoutInflater) + setContentView(binding.root) + + binding.signUpSignUpBtn.setOnClickListener { + signUp() + } + } + + private fun getUser(): User { + val email: String = + binding.signUpIdEt.text.toString() + "@" + binding.signUpDirectInputEt.text.toString() + val name: String = binding.signUpNameEt.text.toString() + val pwd: String = binding.signUpPasswordEt.text.toString() + + return User(email, pwd, name) + } + + + + private fun signUp() { + if (binding.signUpIdEt.text.toString() + .isEmpty() || binding.signUpDirectInputEt.text.toString().isEmpty() + ) { + Toast.makeText(this, "이메일 형식이 잘못되었습니다.", Toast.LENGTH_SHORT).show() + return + } + + if (binding.signUpPasswordEt.text.toString() != binding.signUpPasswordCheckEt.text.toString()) { + Toast.makeText(this, "비밀번호가 일치하지 않습니다.", Toast.LENGTH_SHORT).show() + return + } + + val authService = AuthService() + authService.setSignUpView(this) + + authService.signUp(getUser()) + + Log.d("SIGNUP-ACT/ASYNC", "Hello, FLO") + } + + override fun onSignUpLoading() { + TODO("Not yet implemented") + } + + override fun onSignUpSuccess() { + finish() + } + + override fun onSignUpFailure() { + //실패처리 + } +} \ No newline at end of file diff --git a/mission/Flo/app/src/main/java/com/example/flo/ui/siginup/SignUpView.kt b/mission/Flo/app/src/main/java/com/example/flo/ui/siginup/SignUpView.kt new file mode 100644 index 0000000..4b3a5cc --- /dev/null +++ b/mission/Flo/app/src/main/java/com/example/flo/ui/siginup/SignUpView.kt @@ -0,0 +1,8 @@ +package com.example.udemy_android_template.ui.siginup + +interface SignUpView { + fun onSignUpLoading() + fun onSignUpSuccess() + fun onSignUpFailure() + //fun onSignUpFailure(code: Int, message: String) +} \ No newline at end of file diff --git a/mission/Flo/app/src/main/res/drawable/ic_back.png b/mission/Flo/app/src/main/res/drawable/ic_back.png new file mode 100644 index 0000000..aafa5a6 Binary files /dev/null and b/mission/Flo/app/src/main/res/drawable/ic_back.png differ diff --git a/mission/Flo/app/src/main/res/drawable/selector_navi_home.xml b/mission/Flo/app/src/main/res/drawable/selector_navi_home.xml new file mode 100644 index 0000000..3ed18be --- /dev/null +++ b/mission/Flo/app/src/main/res/drawable/selector_navi_home.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/mission/Flo/app/src/main/res/drawable/selector_navi_match.xml b/mission/Flo/app/src/main/res/drawable/selector_navi_match.xml new file mode 100644 index 0000000..f32483d --- /dev/null +++ b/mission/Flo/app/src/main/res/drawable/selector_navi_match.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/mission/Flo/app/src/main/res/drawable/selector_navi_mypage.xml b/mission/Flo/app/src/main/res/drawable/selector_navi_mypage.xml new file mode 100644 index 0000000..3bad2a1 --- /dev/null +++ b/mission/Flo/app/src/main/res/drawable/selector_navi_mypage.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/mission/Flo/app/src/main/res/drawable/selector_navi_search.xml b/mission/Flo/app/src/main/res/drawable/selector_navi_search.xml new file mode 100644 index 0000000..34fa8a2 --- /dev/null +++ b/mission/Flo/app/src/main/res/drawable/selector_navi_search.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/mission/Flo/app/src/main/res/drawable/udemy_softsquard.png b/mission/Flo/app/src/main/res/drawable/udemy_softsquard.png new file mode 100644 index 0000000..5339fdd Binary files /dev/null and b/mission/Flo/app/src/main/res/drawable/udemy_softsquard.png differ diff --git a/mission/Flo/app/src/main/res/layout/activity_login.xml b/mission/Flo/app/src/main/res/layout/activity_login.xml new file mode 100644 index 0000000..c9167a0 --- /dev/null +++ b/mission/Flo/app/src/main/res/layout/activity_login.xml @@ -0,0 +1,175 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/mission/Flo/app/src/main/res/layout/activity_signup.xml b/mission/Flo/app/src/main/res/layout/activity_signup.xml new file mode 100644 index 0000000..659207d --- /dev/null +++ b/mission/Flo/app/src/main/res/layout/activity_signup.xml @@ -0,0 +1,206 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/mission/Flo/app/src/main/res/layout/fragment_search.xml b/mission/Flo/app/src/main/res/layout/fragment_search.xml new file mode 100644 index 0000000..b842786 --- /dev/null +++ b/mission/Flo/app/src/main/res/layout/fragment_search.xml @@ -0,0 +1,21 @@ + + + + + \ No newline at end of file