first commit
This commit is contained in:
155
services/search_service.go
Normal file
155
services/search_service.go
Normal file
@@ -0,0 +1,155 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"strings"
|
||||
"sync"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// StockItem 검색용 종목 정보
|
||||
type StockItem struct {
|
||||
Code string `json:"code"`
|
||||
Name string `json:"name"`
|
||||
Market string `json:"market"`
|
||||
}
|
||||
|
||||
// SearchService 종목 검색 서비스 (메모리 캐시)
|
||||
type SearchService struct {
|
||||
kiwoom *KiwoomClient
|
||||
mu sync.RWMutex
|
||||
stocks []StockItem // 전체 종목 리스트 캐시
|
||||
}
|
||||
|
||||
var (
|
||||
searchSvcOnce sync.Once
|
||||
searchSvc *SearchService
|
||||
)
|
||||
|
||||
// GetSearchService 싱글턴 반환
|
||||
func GetSearchService() *SearchService {
|
||||
searchSvcOnce.Do(func() {
|
||||
searchSvc = &SearchService{kiwoom: GetKiwoomClient()}
|
||||
})
|
||||
return searchSvc
|
||||
}
|
||||
|
||||
// Load 코스피·코스닥 전체 종목 목록을 키움 ka10099로 로딩
|
||||
// 서버 시작 시 1회 호출 (고루틴)
|
||||
func (s *SearchService) Load() {
|
||||
go func() {
|
||||
stocks, err := s.fetchAll()
|
||||
if err != nil {
|
||||
log.Printf("종목 리스트 로딩 실패: %v", err)
|
||||
return
|
||||
}
|
||||
s.mu.Lock()
|
||||
s.stocks = stocks
|
||||
s.mu.Unlock()
|
||||
log.Printf("종목 리스트 로딩 완료: %d개", len(stocks))
|
||||
}()
|
||||
}
|
||||
|
||||
// Search 종목명 또는 코드로 검색 (최대 10건)
|
||||
func (s *SearchService) Search(q string) []StockItem {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
|
||||
q = strings.TrimSpace(q)
|
||||
if q == "" || len(s.stocks) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
qLower := strings.ToLower(q)
|
||||
isDigit := isAllDigit(q)
|
||||
|
||||
var results []StockItem
|
||||
for _, st := range s.stocks {
|
||||
var match bool
|
||||
if isDigit {
|
||||
// 숫자면 코드 전방일치
|
||||
match = strings.HasPrefix(st.Code, q)
|
||||
} else {
|
||||
// 문자면 종목명 포함검색
|
||||
match = strings.Contains(strings.ToLower(st.Name), qLower)
|
||||
}
|
||||
if match {
|
||||
results = append(results, st)
|
||||
if len(results) >= 10 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
// fetchAll ka10099 연속조회로 코스피+코스닥 전체 종목 수집
|
||||
func (s *SearchService) fetchAll() ([]StockItem, error) {
|
||||
var all []StockItem
|
||||
for _, mrkt := range []struct{ tp, name string }{
|
||||
{"0", "KOSPI"},
|
||||
{"10", "KOSDAQ"},
|
||||
} {
|
||||
items, err := s.fetchMarket(mrkt.tp, mrkt.name)
|
||||
if err != nil {
|
||||
log.Printf("종목 리스트 조회 실패 (%s): %v", mrkt.name, err)
|
||||
continue
|
||||
}
|
||||
all = append(all, items...)
|
||||
}
|
||||
return all, nil
|
||||
}
|
||||
|
||||
// fetchMarket 특정 시장의 전체 종목을 연속조회로 수집
|
||||
func (s *SearchService) fetchMarket(mrktTp, marketName string) ([]StockItem, error) {
|
||||
body := map[string]string{"mrkt_tp": mrktTp}
|
||||
|
||||
var items []StockItem
|
||||
contYn := "N"
|
||||
nextKey := ""
|
||||
|
||||
for {
|
||||
raw, respContYn, respNextKey, err := s.kiwoom.postPaged("ka10099", "/api/dostk/stkinfo", body, contYn, nextKey)
|
||||
if err != nil {
|
||||
return items, err
|
||||
}
|
||||
|
||||
var resp struct {
|
||||
List []struct {
|
||||
Code string `json:"code"`
|
||||
Name string `json:"name"`
|
||||
State string `json:"state"`
|
||||
} `json:"list"`
|
||||
}
|
||||
if err := json.Unmarshal(raw, &resp); err != nil {
|
||||
return items, err
|
||||
}
|
||||
|
||||
for _, it := range resp.List {
|
||||
items = append(items, StockItem{
|
||||
Code: it.Code,
|
||||
Name: it.Name,
|
||||
Market: marketName,
|
||||
})
|
||||
}
|
||||
|
||||
// 연속조회 종료 조건
|
||||
if respContYn != "Y" || respNextKey == "" {
|
||||
break
|
||||
}
|
||||
contYn = "Y"
|
||||
nextKey = respNextKey
|
||||
}
|
||||
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func isAllDigit(s string) bool {
|
||||
for _, r := range s {
|
||||
if !unicode.IsDigit(r) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
Reference in New Issue
Block a user