Resume last login

This commit is contained in:
2023-05-25 05:43:22 +09:00
parent 819fa792aa
commit 3d1ffb20d9
6 changed files with 194 additions and 63 deletions

View File

@@ -33,6 +33,7 @@
341F5F0F2A15223A00962D48 /* SeibroRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 341F5F0E2A15223A00962D48 /* SeibroRequest.swift */; };
341F5F112A1685E700962D48 /* ShopRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 341F5F102A1685E700962D48 /* ShopRequest.swift */; };
341F5F142A16CD7A00962D48 /* DomesticShopProduct.swift in Sources */ = {isa = PBXBuildFile; fileRef = 341F5F132A16CD7A00962D48 /* DomesticShopProduct.swift */; };
349C26AB2A1EAE2400F3EC91 /* KissProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 349C26AA2A1EAE2400F3EC91 /* KissProfile.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -73,6 +74,7 @@
341F5F0E2A15223A00962D48 /* SeibroRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeibroRequest.swift; sourceTree = "<group>"; };
341F5F102A1685E700962D48 /* ShopRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShopRequest.swift; sourceTree = "<group>"; };
341F5F132A16CD7A00962D48 /* DomesticShopProduct.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomesticShopProduct.swift; sourceTree = "<group>"; };
349C26AA2A1EAE2400F3EC91 /* KissProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KissProfile.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -121,6 +123,7 @@
341F5EDF2A0F372000962D48 /* Login */,
341F5EAE2A0A80EC00962D48 /* KissMe.h */,
341F5EAF2A0A80EC00962D48 /* KissMe.docc */,
349C26AA2A1EAE2400F3EC91 /* KissProfile.swift */,
341F5EF42A0F891200962D48 /* KissAccount.swift */,
341F5F0A2A15115400962D48 /* KissShop.swift */,
);
@@ -315,6 +318,7 @@
341F5EE92A0F87FB00962D48 /* DomesticStockPrice.swift in Sources */,
341F5EEE2A0F884300962D48 /* ForeignStockPrice.swift in Sources */,
341F5EDE2A0F300100962D48 /* Request.swift in Sources */,
349C26AB2A1EAE2400F3EC91 /* KissProfile.swift in Sources */,
341F5F012A11155100962D48 /* DomesticStockSearchResult.swift in Sources */,
341F5F142A16CD7A00962D48 /* DomesticShopProduct.swift in Sources */,
341F5EF22A0F887200962D48 /* DomesticFutures.swift in Sources */,

View File

@@ -8,65 +8,12 @@
import Foundation
public class KissAccount {
public class KissAccount: KissProfile {
let credential: Credential
var profileLock = NSLock()
var profile = Profile()
var accessToken: String? {
profileLock.lock()
defer {
profileLock.unlock()
}
return profile.recent?.accessToken
}
public init(credential: Credential) {
self.credential = credential
// TODO: Profile
// accessToken
}
func setAccessToken(_ accessToken: String, expired: Date) {
profileLock.lock()
defer {
profileLock.unlock()
}
profile.recent = Recent(accessToken: accessToken, accessTokenExpired: expired)
}
func resetAccessToken() {
profileLock.lock()
defer {
profileLock.unlock()
}
profile.recent = nil
}
}
extension KissAccount {
public class Profile: Codable {
public var recent: Recent?
public var loves: [Love]
init() {
recent = nil
loves = []
}
}
public struct Recent: Codable {
public let accessToken: String
public let accessTokenExpired: Date
}
public struct Love: Codable {
// TODO: write
super.init()
}
}

141
KissMe/KissProfile.swift Normal file
View File

@@ -0,0 +1,141 @@
//
// KissProfile.swift
// KissMe
//
// Created by ened-book-m1 on 2023/05/25.
//
import Foundation
public class KissProfile {
let profileLock = NSLock()
var profile = Profile()
public var accessToken: String? {
profileLock.lock()
defer {
profileLock.unlock()
}
return profile.recent?.accessToken
}
public var accessTokenExpiredDate: Date? {
profileLock.lock()
defer {
profileLock.unlock()
}
return profile.recent?.accessTokenExpired
}
public var isMock: Bool? {
profileLock.lock()
defer {
profileLock.unlock()
}
return profile.recent?.isMock
}
public init() {
loadProfile()
}
func setAccessToken(_ accessToken: String, expired: Date, isMock: Bool) {
profileLock.lock()
profile.recent = Recent(isMock: isMock, accessToken: accessToken, accessTokenExpired: expired)
profileLock.unlock()
saveProfile()
}
func resetAccessToken() {
profileLock.lock()
profile.recent = nil
profileLock.unlock()
saveProfile()
}
}
extension KissProfile {
public class Profile: Codable {
public var recent: Recent?
public var loves: [String: [Love]]
init() {
recent = nil
loves = [:]
}
}
public struct Recent: Codable {
public let isMock: Bool
public let accessToken: String
public let accessTokenExpired: Date
}
public struct Love: Codable {
let key: String
let iscd: String
let name: String
}
private var profileJsonUrl: URL {
URL.currentDirectory().appending(path: "profile.json")
}
public func loadProfile() {
do {
let data = try Data(contentsOf: profileJsonUrl, options: .uncached)
let profile = try JSONDecoder().decode(Profile.self, from: data)
self.profile = profile
} catch {
if (error as NSError).code != 2 {
print("\(error)")
}
}
}
public func saveProfile() {
do {
let data = try JSONEncoder().encode(profile)
try data.write(to: profileJsonUrl)
} catch {
print("\(error)")
}
}
}
extension KissProfile {
public func addLove(_ love: Love, for key: String) {
profileLock.lock()
defer {
profileLock.unlock()
}
if let _ = profile.loves[key] {
profile.loves[key]?.append(love)
}
else {
profile.loves[key] = [love]
}
}
public func removeLove(_ love: Love, for key: String) -> Bool {
profileLock.lock()
defer {
profileLock.unlock()
}
if let index = profile.loves[key]?.firstIndex(where: { $0.iscd == love.iscd }) {
profile.loves[key]?.remove(at: index)
return true
}
return false
}
}

View File

@@ -123,7 +123,7 @@ extension KissAccount {
request.query { result in
switch result {
case .success(let result):
self.setAccessToken(result.accessToken, expired: result.accessTokenExpiredDate)
self.setAccessToken(result.accessToken, expired: result.accessTokenExpiredDate, isMock: self.credential.isMock)
continuation.resume(returning: true)
case .failure(let error):
continuation.resume(throwing: error)

View File

@@ -43,8 +43,9 @@ class KissConsole {
case showcase = "showcase"
//
case love = "love" // love nuts.1
case hate = "hate" // hate nuts.1
case loves = "loves" //
case love = "love" // love nuts.1 ISCD
case hate = "hate" // hate nuts.1 ISCD
var needLogin: Bool {
switch self {
@@ -56,7 +57,7 @@ class KissConsole {
return false
case .showcase:
return false
case .love, .hate:
case .loves, .love, .hate:
return false
case .openBag:
return false
@@ -74,6 +75,7 @@ class KissConsole {
createSubpath("log")
createSubpath("data")
lastLogin()
Task {
await onLoadShop()
@@ -200,6 +202,22 @@ extension KissConsole {
let totalCount = products.reduce(0, { $0 + $1.value.count })
print("load products \(totalCount) with \(products.count) key")
}
private func lastLogin() {
let profile = KissProfile()
guard let isMock = profile.isMock else {
return
}
do {
credential = try KissCredential(isMock: isMock)
account = KissAccount(credential: credential!)
let expiredAt = account?.accessTokenExpiredDate?.yyyyMMdd_HHmmss_forTime ?? ""
print("resume \(isMock ? "mock login": "real login") expired at \(expiredAt)")
} catch {
print("\(error)")
}
}
}
extension KissConsole {
@@ -283,9 +301,7 @@ extension KissConsole {
// TODO: work
do {
try await account?.cancelOrder() { result in
}
let success = try await account?.cancelOrder()
} catch {
print("\(error)")
}

View File

@@ -1,11 +1,22 @@
## About KissMe
# About KissMe
KissMe 는 KIS API 를 연동한 swift 라이브러리입니다.
# KissMeConsole
KissMeConsole 은 command line 에서 인터렉티브 명령어로 API 호출을 테스트해볼 수 있는 도구입니다.
KissMeConsole 에서 유효한 command line 명령어는 다음과 같습니다.
## command list
# KissCredential
KissCredential 에서 사용하는 json 의 양식은 다음과 같습니다.
### mock-server.json, real-server.json
```json
{
"isMock": false,
@@ -19,3 +30,15 @@ KissCredential 에서 사용하는 json 의 양식은 다음과 같습니다.
* `accountNo` 에는 계좌번호를 의미합니다. 8-2 형태의 숫자로 입력합니다.
* `appKey` 는 한국투자증권 홈페이지에서 발급받은 appkey 입니다.
* `appSecret` 는 한국투자증권 홈페이지에서 발급받은 appsecret 입니다.
### shop-server.json
```json
{
"openApiKey": "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"
}
```
* `openApiKey` 는 [data.go.kr](https://www.data.go.kr/) 에서 발급받은 API 인증키입니다.
* 다음의 Open API 활용신청을 합니다.
* [금융위원회_KRX상장종목정보](https://www.data.go.kr/tcs/dss/selectApiDataDetailView.do?publicDataPk=15094775)