286 lines
6.9 KiB
Go
286 lines
6.9 KiB
Go
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)
|
|
}
|