Files
iotlcm/onelink-mock/internal/service/store_sqlite.go
T

246 lines
10 KiB
Go

package service
import (
"context"
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"sync/atomic"
"time"
"git.5iot.com/iotlcm-public/iotlcm-onelink-mock/internal/dao"
"git.5iot.com/iotlcm-public/iotlcm-onelink-mock/internal/model/do"
"git.5iot.com/iotlcm-public/iotlcm-onelink-mock/internal/model/entity"
"git.5iot.com/iotlcm-public/iotlcm-onelink-mock/manifest"
_ "github.com/gogf/gf/contrib/drivers/sqlite/v2"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/errors/gerror"
)
var sqliteStoreSequence atomic.Uint64
type sqliteStoreBackend struct {
db gdb.DB
ctx context.Context
keepOpen bool
}
func newSQLiteStoreBackend(databaseLink string) (storeBackend, error) {
if !strings.HasPrefix(databaseLink, "sqlite:") {
if strings.TrimSpace(databaseLink) == "" {
databaseLink = filepath.Join("data", "onelink_mock.db")
}
if databaseLink != ":memory:" {
if err := os.MkdirAll(filepath.Dir(databaseLink), 0o755); err != nil {
return nil, gerror.Wrap(err, "create sqlite directory")
}
}
databaseLink = fmt.Sprintf("sqlite::@file(%s)", filepath.ToSlash(databaseLink))
}
group := fmt.Sprintf("onelink_sqlite_%d", sqliteStoreSequence.Add(1))
if err := gdb.AddConfigNode(group, gdb.ConfigNode{
Type: "sqlite", Link: databaseLink,
MaxIdleConnCount: 1, MaxOpenConnCount: 1, MaxConnLifeTime: 24 * time.Hour,
}); err != nil {
return nil, gerror.Wrap(err, "configure sqlite")
}
db, err := gdb.NewByGroup(group)
if err != nil {
return nil, gerror.Wrap(err, "open sqlite")
}
backend := &sqliteStoreBackend{db: db, ctx: context.Background(), keepOpen: strings.Contains(databaseLink, ":memory:")}
if err = backend.ensureSchema(); err != nil {
return nil, err
}
if !backend.keepOpen {
if err = db.Close(backend.ctx); err != nil {
return nil, gerror.Wrap(err, "close sqlite after initialization")
}
}
return backend, nil
}
func (b *sqliteStoreBackend) close() error {
if b.keepOpen {
return nil
}
if err := b.db.Close(b.ctx); err != nil {
return gerror.Wrap(err, "close sqlite connection")
}
return nil
}
func (b *sqliteStoreBackend) ensureSchema() error {
if _, err := b.db.Exec(b.ctx, manifest.SchemaSQL); err != nil {
return gerror.Wrap(err, "initialize sqlite schema")
}
rows, err := b.db.GetAll(b.ctx, "PRAGMA table_info(mock_t_sim_card)")
if err != nil {
return gerror.Wrap(err, "inspect sqlite schema")
}
columns := make(map[string]struct{}, len(rows))
for _, row := range rows {
columns[row["name"].String()] = struct{}{}
}
migrations := map[string]string{
"group_id": "ALTER TABLE mock_t_sim_card ADD COLUMN group_id INTEGER",
"stop_reason": "ALTER TABLE mock_t_sim_card ADD COLUMN stop_reason TEXT",
"frozen_status": "ALTER TABLE mock_t_sim_card ADD COLUMN frozen_status TEXT",
"frozen_reason": "ALTER TABLE mock_t_sim_card ADD COLUMN frozen_reason TEXT",
"frozen_oper_time": "ALTER TABLE mock_t_sim_card ADD COLUMN frozen_oper_time TEXT",
"exported": "ALTER TABLE mock_t_sim_card ADD COLUMN exported INTEGER NOT NULL DEFAULT 0",
"export_checked_at": "ALTER TABLE mock_t_sim_card ADD COLUMN export_checked_at TEXT",
}
for column, statement := range migrations {
if _, ok := columns[column]; ok {
continue
}
if _, err = b.db.Exec(b.ctx, statement); err != nil {
return gerror.Wrapf(err, "add sqlite column %s", column)
}
}
return nil
}
func (b *sqliteStoreBackend) Load() (Store, error) {
defer b.close()
data := defaultStore()
var groups []entity.MockCardGroup
if err := b.db.Model(dao.MockCardGroup.Table()).Ctx(b.ctx).OrderAsc(dao.MockCardGroup.Columns().Id).Scan(&groups); err != nil {
return Store{}, gerror.Wrap(err, "load card groups")
}
data.Groups = make([]CardGroup, 0, len(groups))
groupNames := make(map[int64]string, len(groups))
for _, row := range groups {
group := CardGroup{ID: int64(row.Id), Name: row.Name, CreatedAt: row.CreatedAt, UpdatedAt: row.UpdatedAt}
data.Groups = append(data.Groups, group)
groupNames[group.ID] = group.Name
}
var rows []entity.MockTSimCard
if err := b.db.Model(dao.MockTSimCard.Table()).Ctx(b.ctx).OrderAsc(dao.MockTSimCard.Columns().Id).Scan(&rows); err != nil {
return Store{}, gerror.Wrap(err, "load sim cards")
}
data.Cards = make([]SIMCard, 0, len(rows))
for _, row := range rows {
card := SIMCard{
GroupID: int64(row.GroupId), GroupName: groupNames[int64(row.GroupId)],
MSISDN: row.Msisdn, ICCID: row.Iccid, IMSI: row.Imsi, IMEI: row.Imei, Remark: row.Remark,
CardStatus: row.CardStatus, LastChangeDate: row.LastChangeDate, StopReason: row.StopReason,
FrozenStatus: row.FrozenStatus, FrozenReason: row.FrozenReason, FrozenOperTime: row.FrozenOperTime,
OpenDate: row.OpenDate, ActiveDate: row.ActiveDate, OnOffStatus: row.OnOffStatus,
SessionStatus: row.SessionStatus, SessionCreateDate: row.SessionCreateDate,
IP: row.SessionIpv4, IPv6Prefix: row.SessionIpv6Prefix, IPv6: row.SessionIpv6, RAT: row.Rat,
CardBindStatus: row.CardBindStatus, RegionLimitStatus: row.RegionLimitStatus,
AreaRestrictType: row.AreaRestrictType, RestrictedArea: row.RestrictedArea,
MonthUsageKB: float64(row.CurrentMonthUsageKb), PoolUsageBytes: float64(row.PoolUsageBytes),
DailyBaseKB: float64(row.DailyUsageKb), MonthlyBaseKB: float64(row.MonthlyUsageKb),
GrowthPerMinuteKB: float64(row.GrowthPerMinuteKb), LastUsageUpdateTime: dbTimeToRFC3339(row.LastUsageUpdateTime),
Exported: row.Exported != 0, ExportCheckedAt: row.ExportCheckedAt,
}
card.APNs = parseAPNs(row.ApnListJson, row.PrimaryApnName, row.PrimaryApnStatus, float64(row.PrimaryApnUsageKb), int64(row.PrimaryApnUsageBytes))
data.Cards = append(data.Cards, card)
}
var orders []entity.MockOrder
if err := b.db.Model(dao.MockOrder.Table()).Ctx(b.ctx).OrderAsc(dao.MockOrder.Columns().Id).Scan(&orders); err != nil {
return Store{}, gerror.Wrap(err, "load orders")
}
data.Orders = make([]Order, 0, len(orders))
for _, row := range orders {
data.Orders = append(data.Orders, Order{OrderNum: row.OrderNum, MSISDN: row.Msisdn, ICCID: row.Iccid, IMSI: row.Imsi, Type: row.Type, OperType: row.OperType, APNName: row.ApnName, Status: row.Status, CreatedAt: row.CreatedAt})
}
return data, nil
}
func (b *sqliteStoreBackend) Save(data Store) error {
defer b.close()
return b.db.Transaction(b.ctx, func(ctx context.Context, tx gdb.TX) error {
if _, err := tx.Exec(manifest.SchemaSQL); err != nil {
return gerror.Wrap(err, "initialize sqlite schema")
}
for _, table := range []string{dao.MockOrder.Table(), dao.MockTSimCard.Table(), dao.MockCardGroup.Table()} {
if _, err := tx.Model(table).Ctx(ctx).Where("1=1").Delete(); err != nil {
return gerror.Wrap(err, "clear sqlite table")
}
}
if err := insertGroups(ctx, tx, data.Groups); err != nil {
return err
}
if err := insertCards(ctx, tx, data.Cards); err != nil {
return err
}
return insertOrders(ctx, tx, data.Orders)
})
}
func (b *sqliteStoreBackend) AppendCards(cards []SIMCard) error {
defer b.close()
return b.db.Transaction(b.ctx, func(ctx context.Context, tx gdb.TX) error {
if _, err := tx.Exec(manifest.SchemaSQL); err != nil {
return gerror.Wrap(err, "initialize sqlite schema")
}
return insertCards(ctx, tx, cards)
})
}
func insertGroups(ctx context.Context, tx gdb.TX, groups []CardGroup) error {
for _, item := range groups {
if _, err := tx.Model(dao.MockCardGroup.Table()).Ctx(ctx).Data(do.MockCardGroup{Id: item.ID, Name: item.Name, CreatedAt: item.CreatedAt, UpdatedAt: item.UpdatedAt}).Insert(); err != nil {
return gerror.Wrap(err, "insert card group")
}
}
return nil
}
func insertCards(ctx context.Context, tx gdb.TX, cards []SIMCard) error {
for _, item := range cards {
normalizeCard(&item)
primary := primaryAPN(item)
apnList, err := json.Marshal(apnsForDB(item.APNs))
if err != nil {
return gerror.Wrap(err, "encode apn list")
}
carrier, packageCode, packageName := mockMetadataFromRemark(item.Remark)
data := do.MockTSimCard{
GroupId: item.GroupID, Msisdn: item.MSISDN, Iccid: item.ICCID, Imsi: item.IMSI, Imei: item.IMEI,
Remark: item.Remark, Carrier: carrier, PackageCode: packageCode, PackageName: packageName,
PackageStatus: 1, PackageTotalFlowMb: item.MonthlyBaseKB / 1024, IsFlowPool: 1,
CardStatus: item.CardStatus, LastChangeDate: item.LastChangeDate, StopReason: item.StopReason,
FrozenStatus: item.FrozenStatus, FrozenReason: item.FrozenReason, FrozenOperTime: item.FrozenOperTime,
OpenDate: item.OpenDate, ActiveDate: item.ActiveDate, OnOffStatus: item.OnOffStatus, SessionStatus: item.SessionStatus,
ApnId: primary.Name, SessionIpv4: item.IP, SessionIpv6Prefix: item.IPv6Prefix, SessionIpv6: item.IPv6,
SessionCreateDate: item.SessionCreateDate, Rat: item.RAT, ApnListJson: string(apnList), ApnUsageJson: string(apnList),
PrimaryApnName: primary.Name, PrimaryApnStatus: primary.Status, PrimaryApnUsageKb: primary.UsageBytes / 1024,
PrimaryApnUsageBytes: int64(primary.UsageBytes), PccCode: "default", PccUsageKb: primary.UsageBytes / 1024,
CurrentMonthUsageKb: item.MonthUsageKB, PoolUsageBytes: int64(item.PoolUsageBytes), DailyUsageKb: item.DailyBaseKB,
MonthlyUsageKb: item.MonthlyBaseKB, DailyQueryDate: parseCardTime(item.ActiveDate).Format("20060102"),
MonthlyQueryMonth: parseCardTime(item.ActiveDate).Format("200601"), MonthlyDeadline: parseCardTime(item.ActiveDate).AddDate(0, 0, -1).Format("20060102"),
GrowthPerMinuteKb: item.GrowthPerMinuteKB, AutoGrowthEnabled: 0, LastUsageUpdateTime: rfc3339ToDBTime(item.LastUsageUpdateTime),
CardBindStatus: item.CardBindStatus, RegionLimitStatus: item.RegionLimitStatus, AreaRestrictType: item.AreaRestrictType,
RestrictedArea: item.RestrictedArea, Exported: boolInt(item.Exported), ExportCheckedAt: item.ExportCheckedAt,
CreatedAt: item.OpenDate, UpdatedAt: item.LastChangeDate,
}
if _, err = tx.Model(dao.MockTSimCard.Table()).Ctx(ctx).Data(data).Insert(); err != nil {
return gerror.Wrap(err, "insert sim card")
}
}
return nil
}
func insertOrders(ctx context.Context, tx gdb.TX, orders []Order) error {
for _, item := range orders {
if _, err := tx.Model(dao.MockOrder.Table()).Ctx(ctx).Data(do.MockOrder{OrderNum: item.OrderNum, Msisdn: item.MSISDN, Iccid: item.ICCID, Imsi: item.IMSI, Type: item.Type, OperType: item.OperType, ApnName: item.APNName, Status: item.Status, CreatedAt: item.CreatedAt}).Insert(); err != nil {
return gerror.Wrap(err, "insert order")
}
}
return nil
}
func boolInt(value bool) int {
if value {
return 1
}
return 0
}