package service import ( "crypto/rand" "encoding/hex" "encoding/json" "fmt" "strings" "sync" "time" ) type StoreManager struct { mu sync.Mutex backend storeBackend data Store initialized bool } type storeBackend interface { Load() (Store, error) Save(Store) error } type generatedCardBackend interface { AppendCards([]SIMCard) error } func NewStoreManager(path string) (*StoreManager, error) { backend, err := newStoreBackend(path) if err != nil { return nil, err } sm := &StoreManager{backend: backend} if err := sm.load(); err != nil { return nil, err } return sm, nil } func (s *StoreManager) Snapshot() Store { s.mu.Lock() defer s.mu.Unlock() return cloneStore(s.data) } func (s *StoreManager) SaveMutation(fn func(*Store) error) error { s.mu.Lock() defer s.mu.Unlock() if err := fn(&s.data); err != nil { return err } return s.saveLocked() } func (s *StoreManager) WithStore(fn func(*Store) error) error { s.mu.Lock() defer s.mu.Unlock() return fn(&s.data) } func (s *StoreManager) Persist() error { s.mu.Lock() defer s.mu.Unlock() return s.saveLocked() } func (s *StoreManager) GenerateCards(count int) (GenerateResult, error) { return s.GenerateCardsInGroup(count, 0) } func (s *StoreManager) GenerateCardsInGroup(count int, groupID int64) (GenerateResult, error) { s.mu.Lock() defer s.mu.Unlock() if count <= 0 { return GenerateResult{Total: len(s.data.Cards)}, nil } generator, ok := s.backend.(generatedCardBackend) if !ok { return GenerateResult{}, fmt.Errorf("current store backend does not support card generation") } if groupID != 0 { found := false for _, group := range s.data.Groups { if group.ID == groupID { found = true break } } if !found { return GenerateResult{}, fmt.Errorf("card group not found") } } cards := GenerateSIMCards(time.Now(), count) for i := range cards { cards[i].GroupID = groupID for _, group := range s.data.Groups { if group.ID == groupID { cards[i].GroupName = group.Name break } } } if err := generator.AppendCards(cards); err != nil { return GenerateResult{}, err } s.data.Cards = append(s.data.Cards, cards...) return GenerateResult{Generated: len(cards), Total: len(s.data.Cards)}, nil } func (s *StoreManager) QueryCards(keyword, exportStatus string, page, pageSize int) CardPage { return s.QueryCardsByGroup(keyword, exportStatus, 0, false, page, pageSize) } func (s *StoreManager) QueryCardsByGroup(keyword, exportStatus string, groupID int64, filterGroup bool, page, pageSize int) CardPage { s.mu.Lock() defer s.mu.Unlock() if page < 1 { page = 1 } if pageSize < 1 { pageSize = 20 } if pageSize > 100 { pageSize = 100 } matched := filterCards(s.data.Cards, keyword, exportStatus, groupID, filterGroup) start := (page - 1) * pageSize if start > len(matched) { start = len(matched) } end := start + pageSize if end > len(matched) { end = len(matched) } return CardPage{Items: append([]SIMCard{}, matched[start:end]...), Total: len(matched), Page: page, PageSize: pageSize} } func (s *StoreManager) QueryAllCardsByGroup(keyword, exportStatus string, groupID int64, filterGroup bool) []SIMCard { s.mu.Lock() defer s.mu.Unlock() return append([]SIMCard{}, filterCards(s.data.Cards, keyword, exportStatus, groupID, filterGroup)...) } func filterCards(cards []SIMCard, keyword, exportStatus string, groupID int64, filterGroup bool) []SIMCard { keyword = strings.TrimSpace(keyword) matched := make([]SIMCard, 0) for i := len(cards) - 1; i >= 0; i-- { card := cards[i] groupMatch := !filterGroup || card.GroupID == groupID exportMatch := exportStatus == "" || (exportStatus == "exported" && card.Exported) || (exportStatus == "not_exported" && !card.Exported) if groupMatch && exportMatch && (keyword == "" || card.MSISDN == keyword || card.ICCID == keyword || card.IMSI == keyword) { matched = append(matched, card) } } return matched } func (s *StoreManager) QueryOrders(page, pageSize int) OrderPage { s.mu.Lock() defer s.mu.Unlock() if page < 1 { page = 1 } if pageSize < 1 { pageSize = 20 } if pageSize > 100 { pageSize = 100 } total := len(s.data.Orders) start := (page - 1) * pageSize if start > total { start = total } end := start + pageSize if end > total { end = total } items := make([]Order, 0, end-start) for i := total - 1 - start; i >= total-end; i-- { items = append(items, s.data.Orders[i]) } return OrderPage{Items: items, Total: total, Page: page, PageSize: pageSize} } func (s *StoreManager) load() error { return s.reloadLocked() } func (s *StoreManager) reloadLocked() error { data, err := s.backend.Load() if err != nil { return err } if s.initialized { data.AppIDs = s.data.AppIDs data.Tokens = s.data.Tokens data.Orders = s.data.Orders data.Settings = s.data.Settings } s.data = data if s.data.Tokens == nil { s.data.Tokens = map[string]Token{} } s.initialized = true return nil } func (s *StoreManager) saveLocked() error { return s.backend.Save(s.data) } func defaultStore() Store { now := time.Now() return Store{ AppIDs: []Credential{ { AppID: "demo_appid", Password: "demo_password", Name: "默认测试应用", Enabled: true, CreatedAt: now.Format("2006-01-02 15:04:05"), }, }, Cards: []SIMCard{}, Groups: []CardGroup{}, Tokens: map[string]Token{}, Orders: []Order{}, Settings: SimulationSettings{ AutoGrowthEnabled: false, DefaultGrowthKB: 0, }, } } func sampleCard(msisdn, iccid, imsi, imei, remark string, monthKB float64) SIMCard { now := time.Now() return SIMCard{ MSISDN: msisdn, ICCID: iccid, IMSI: imsi, IMEI: imei, Remark: remark, CardStatus: "2", LastChangeDate: now.AddDate(0, -1, 0).Format("2006-01-02 15:04:05"), OpenDate: now.AddDate(-1, 0, 0).Format("2006-01-02 15:04:05"), ActiveDate: now.AddDate(-1, 0, 3).Format("2006-01-02 15:04:05"), OnOffStatus: "1", SessionStatus: "01", IP: fmt.Sprintf("10.8.%d.%d", len(msisdn), now.Second()+20), IPv6Prefix: "", IPv6: "", RAT: "6", CardBindStatus: "1", RegionLimitStatus: "0", AreaRestrictType: "1", RestrictedArea: "200", MonthUsageKB: monthKB, PoolUsageBytes: monthKB * 1024, DailyBaseKB: monthKB / 30, MonthlyBaseKB: monthKB, GrowthPerMinuteKB: 1.5, LastUsageUpdateTime: now.Format(time.RFC3339), APNs: []APN{ {Name: "CMIOT", Status: "1", UsageBytes: monthKB * 1024 * 0.7}, {Name: "CMMTM", Status: "1", UsageBytes: monthKB * 1024 * 0.3}, }, } } func cloneStore(src Store) Store { b, _ := json.Marshal(src) var out Store _ = json.Unmarshal(b, &out) return out } func randomHex(n int) string { b := make([]byte, n) if _, err := rand.Read(b); err != nil { return fmt.Sprintf("%d", time.Now().UnixNano()) } return hex.EncodeToString(b) }