first commit

This commit is contained in:
hayato5246
2026-03-31 19:32:59 +09:00
commit d10b794c9f
78 changed files with 1671595 additions and 0 deletions

155
services/search_service.go Normal file
View 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
}