[♻️] 엑세스 토큰으로 계정 정보 확인하는 로직 전체 변경

This commit is contained in:
김선규 2025-03-18 14:59:25 +09:00
parent fea29d0cb8
commit 966dd4cb34
7 changed files with 165 additions and 103 deletions

View File

@ -99,10 +99,11 @@ struct IntroView: View {
private func loadVersion() -> Future<VersionData, Error> { private func loadVersion() -> Future<VersionData, Error> {
return Future { promise in return Future { promise in
APIManager.shared.loadAPIData(url: "\(API_URL)", let request = APIRequest(path: "/api/v1/in/app/version",
path: "/api/v1/in/app/version", parameters: ["type":"I"],
parameters: ["type":"I"], decoding: APIResponse<VersionData>.self)
decodingType: APIResponse<VersionData>.self)
APIManager.shared.loadAPIData(request)
.sink { completion in .sink { completion in
switch completion { switch completion {
case .failure(let error): case .failure(let error):

View File

@ -0,0 +1,29 @@
//
// API Request.swift
// AcaMate
//
// Created by TAnine on 3/18/25.
//
import Foundation
import Alamofire
public struct APIRequest<T: APIResponseProtocol> {
let url, path: String
let method: HTTPMethod
var parameters: [String: Any]
let headers: HTTPHeaders
let decoding: T.Type
init(url: String = "\(API_URL)", path: String,
method: HTTPMethod = .get, headers: HTTPHeaders = [:],
parameters: [String : Any] = [:], decoding: T.Type) {
self.url = url
self.path = path
self.method = method
self.headers = headers
self.parameters = parameters
self.decoding = decoding
}
}

View File

@ -6,13 +6,16 @@
// //
import Foundation import Foundation
public protocol APIResponseProtocol: Decodable {
var status: Status { get }
}
class APIResponse<T: Codable>: Codable { class APIResponse<T: Codable>: Codable, APIResponseProtocol {
let status: Status let status: Status
let data: T? let data: T?
} }
class Status: Codable { public class Status: Codable {
let code: APICode let code: APICode
let message: String let message: String
} }
@ -58,6 +61,8 @@ enum APICode: Codable, RawRepresentable {
} }
} }
// /api/v1/in/app/version ---------------- // /api/v1/in/app/version ----------------
class VersionData: Codable { class VersionData: Codable {
@ -71,7 +76,15 @@ class Access: Codable {
let access: String let access: String
} }
// /api/v1/in/user ----------------
class User: Codable {
let uid, name, type: String
let device_id, push_token: String?
let auto_login_yn: Bool
let login_date: String
let birth: String?
}
// /api/v1/in/user/login ---------------- // /api/v1/in/user/login ----------------

View File

@ -18,56 +18,42 @@ class LoginViewModel: ObservableObject {
var bidArray: [String] = [] var bidArray: [String] = []
func USERPAITEST() { func USERPAITEST() {
@UserDefault(key: "token", defaultValue: "accToken") var accToken
@UserDefault(key: "refresh", defaultValue: "refreshToken") var refresh @UserDefault(key: "refresh", defaultValue: "refreshToken") var refresh
var acc = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJBTVRlc3RFbWFpbDIwMjUwMzE3IiwianRpIjoiYTQzN2Q4YjUtNzQyMi00NWVhLWFiNTktZWI2MTBkNDMwNWRlIiwiZXhwIjoxNzQyMjAwODQ3LCJpc3MiOiJBY2FNYXRlIiwiYXVkIjoiaHR0cHM6L2RldmFjYW1hdGUuaXBzdGVpbi5teWRzLm1lIn0.Y-xxdQWyu3BX_y5hlwWjAdxG5-QRZyHEgJ6T2pDkFRc" let acc = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJBTVRlc3RFbWFpbDIwMjUwMzE4IiwianRpIjoiMjgwMjIwZDMtYzUwNS00YjFjLTgwMzgtZjBlNGJjYzM4MTE3IiwiZXhwIjoxNzQyMjYzMzgwLCJpc3MiOiJBY2FNYXRlIiwiYXVkIjoiaHR0cHM6L2RldmFjYW1hdGUuaXBzdGVpbi5teWRzLm1lIn0.f6kLKnsWhzlllSuYKxpFNuXuV4vOtJ2ox4IGSnxE67Y"
refresh = "tsLeVEtZTjy8iWDvT9vf7Kv7qQr3WgPjeNkFdMb9Rv4=" refresh = "MRo+1HIvaPgECXrvwmGvtUpxSk7Pip7KtGSoWDqmjVA="
APIManager.shared.loadAPIData(url: "\(API_URL)", let request = APIRequest(path: "/api/v1/in/user",
path: "/api/v1/in/user/cancel", parameters: ["token": accToken],
parameters: ["token": acc], decoding: APIResponse<User>.self)
decodingType: APIResponse<String>.self)
.flatMap { response -> AnyPublisher<APIResponse<String>, Error> in APIManager.shared.loadUserAPIData(request: request)
switch response.status.code { .sink { completion in
case .inputErr(let code) where code == "101": switch completion {
return APIManager.shared.reloadAccessToken() case .failure(let error):
.flatMap { newAcc -> AnyPublisher<APIResponse<String>, Error> in printLog("최종 에러: \(error)")
APIManager.shared.loadAPIData(url: "\(API_URL)", case .finished:
path: "/api/v1/in/user/cancel", break
parameters: ["token": (newAcc as! Access).access], }
decodingType: APIResponse<String>.self) } receiveValue: { response in
.eraseToAnyPublisher() guard let user = response.data as? User else { return }
} printLog("최종 값 : \(user.name), \(user.birth)")
.eraseToAnyPublisher()
default:
return Just(response)
.setFailureType(to: Error.self)
.eraseToAnyPublisher()
} }
} .store(in: &cancellables)
.sink { completion in
switch completion {
case .failure(let error):
printLog("최종 \(error)")
case .finished:
break
}
} receiveValue: { response in
printLog("최종: \(response)")
}
.store(in: &cancellables)
} }
func loginAction(type: SNSLoginType) { func loginAction(type: SNSLoginType) {
LoginController().login(type) LoginController().login(type)
.flatMap{ snsId in .flatMap{ snsId in
APIManager.shared.loadAPIData(url: "\(API_URL)", APIManager.shared.loadAPIData(APIRequest(path: "/api/v1/in/user/login",
path: "/api/v1/in/user/login", parameters: [
parameters: [ "acctype": "\(type == .Apple ? "ST00": "ST01")",
"acctype": "\(type == .Apple ? "ST00": "ST01")", "sns_id": "\(snsId.snsId)"
"sns_id": "\(snsId.snsId)" ],
], decoding: APIResponse<User_Token>.self))
decodingType: APIResponse<User_Token>.self)
.map { response in .map { response in
return (snsId: snsId.snsId, response: response) return (snsId: snsId.snsId, response: response)
} }

View File

@ -18,11 +18,12 @@ class SelectAcademyViewModel: ObservableObject {
func loadAcademy() { func loadAcademy() {
@UserDefault(key: "token", defaultValue: "accToken") var token @UserDefault(key: "token", defaultValue: "accToken") var token
@UserDefault(key: "refresh", defaultValue: "refreshToken") var refresh @UserDefault(key: "refresh", defaultValue: "refreshToken") var refresh
APIManager.shared.loadAPIData(url: "\(API_URL)",
path: "/api/v1/in/user/academy", let request = APIRequest(path: "/api/v1/in/user/academy",
method: .get, parameters: ["token": token, "refresh": refresh],
parameters: ["token": token, "refresh": refresh], decoding: APIResponse<[AcademyName]>.self)
decodingType: APIResponse<[AcademyName]>.self)
APIManager.shared.loadAPIData(request)
.sink { completion in .sink { completion in
switch completion { switch completion {
case .failure(let error): case .failure(let error):

View File

@ -14,29 +14,26 @@ public class APIManager {
private var cancellables = Set<AnyCancellable>() private var cancellables = Set<AnyCancellable>()
public static let shared = APIManager() public static let shared = APIManager()
@UserDefault(key: "refresh", defaultValue: "refreshToken") var refresh
@UserDefault(key: "token", defaultValue: "accToken") var accToken
private init(cancellables: Set<AnyCancellable> = Set<AnyCancellable>()) { private init(cancellables: Set<AnyCancellable> = Set<AnyCancellable>()) {
self.cancellables = cancellables self.cancellables = cancellables
} }
public func loadAPIData<T: Decodable>(url: String, path: String, public func loadAPIData<T: APIResponseProtocol>(_ request: APIRequest<T>) -> Future<T, Error> {
method: HTTPMethod = .get, let encoding: ParameterEncoding = (request.method == .get) ? URLEncoding.default : JSONEncoding.default
parameters: [String: Any],
headers: HTTPHeaders = [:],//["Accept": "application/json"],
decodingType: T.Type) -> Future<T, Error> {
let encoding: ParameterEncoding = (method == .get) ? URLEncoding.default : JSONEncoding.default
return Future { promise in return Future { promise in
printLog(parameters) printLog(request.parameters)
AF.request("\(url)\(path)", AF.request("\(request.url)\(request.path)",
method: method, method: request.method,
parameters: parameters, parameters: request.parameters,
encoding: encoding, encoding: encoding,
headers: headers headers: request.headers
) )
.validate(statusCode: 200 ..< 300) .validate(statusCode: 200 ..< 300)
// .responseString { response in .responseDecodable(of: request.decoding) { response in
// printLog(response)
// }
.responseDecodable(of: decodingType) { response in
switch response.result { switch response.result {
case .success(let value): case .success(let value):
@ -53,48 +50,83 @@ public class APIManager {
} }
public func reloadAccessToken() -> Future<Any, Error> { public func reloadAccessToken() -> Future<Any, Error> {
@UserDefault(key: "refresh", defaultValue: "refreshToken") var refresh
return Future { [weak self] promise in return Future { [weak self] promise in
guard let self = self else {return} guard let self = self else {return}
APIManager.shared.loadAPIData(url: "\(API_URL)",
path: "/api/v1/in/app/retryAccess", let request = APIRequest.init(path: "/api/v1/in/app/retryAccess",
parameters: ["refresh": refresh], parameters: ["refresh": refresh],
decodingType: APIResponse<Access>.self) decoding: APIResponse<Access>.self)
.sink { completion in
switch completion { APIManager.shared.loadAPIData(request)
case .failure(let error): .sink { completion in
promise(.failure(error)) switch completion {
printLog("\(error)") case .failure(let error):
case .finished: promise(.failure(error))
printLog("엑세스 토큰 재발급 완료") printLog("\(error)")
break case .finished:
} printLog("엑세스 토큰 재발급 완료")
} receiveValue: { response in break
guard let accToken = response as? APIResponse<Access> else { }
promise(.failure(ACA_ERROR("Unknown ERROR"))) } receiveValue: { response in
return guard let accToken = response as? APIResponse<Access> else {
} promise(.failure(ACA_ERROR("Unknown ERROR")))
return
switch accToken.status.code { }
case .success(let code):
if code == "000" { switch accToken.status.code {
if let tknData = accToken.data { promise(.success(tknData)) } case .success(let code):
if code == "000" {
if let tknData = accToken.data { promise(.success(tknData)) }
}
case .inputErr(let code):
//
promise(.failure(ACA_ERROR("Refresh token ERROR(\(code), \(accToken.status.message)")))
default:
//
promise(.failure(ACA_ERROR("Unknown ERROR")))
} }
case .inputErr(let code):
//
promise(.failure(ACA_ERROR("Refresh token ERROR(\(code), \(accToken.status.message)")))
default:
//
promise(.failure(ACA_ERROR("Unknown ERROR")))
} }
} .store(in: &self.cancellables)
.store(in: &self.cancellables) }
}
public func loadUserAPIData<T: APIResponseProtocol & Codable>(request: APIRequest<T>) -> Future<T, Error>{
return Future { [weak self] promise in
guard let self = self else {return}
printLog(request.parameters["token"])
loadAPIData(request)
.flatMap { (response: T) -> AnyPublisher<T, Error> in
switch response.status.code {
case .inputErr(let code) where code == "101":
return self.reloadAccessToken()
.flatMap { response -> AnyPublisher<T, Error> in
self.accToken = (response as! Access).access
var updateRequest = request
updateRequest.parameters["token"] = self.accToken
return APIManager.shared.loadAPIData(updateRequest)
.eraseToAnyPublisher()
}
.eraseToAnyPublisher()
default:
return Just(response)
.setFailureType(to: Error.self)
.eraseToAnyPublisher()
}
}
.sink { completion in
switch completion {
case .failure(let error):
promise(.failure(error))
case .finished:
break
}
} receiveValue: { finalResponse in
promise(.success(finalResponse))
}
.store(in: &self.cancellables)
} }
} }
} }