chore: 初始开源发布
iotlcm-onelink-mock:OneLink 挡板服务(本地测试替身)。 - Go module 迁移至 git.5iot.com/iotlcm-public/iotlcm-onelink-mock - 修复同毫秒内多次生成卡导致 ICCID/IMSI 唯一约束冲突 - 补 Apache-2.0 LICENSE,README 重写并新增 docs/deploy.md - 移除内部部署资产(CI 触发器 / 镜像推送脚本 / 指定环境部署文档) - 测试凭据改为 demo_appid / demo_password,真实手机号与内网 IP 脱敏 - 统一 LF 行尾并加 .gitattributes
This commit is contained in:
@@ -0,0 +1,245 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user