82466d587d
iotlcm-upstream-proxy:渠道缓存与转发网关。 - 内联原私有依赖:yunze-public → internal/pub/(18 文件), yunze-pb/pb → internal/pb/(9 文件);Consul 注册改用官方 gogf/gf/contrib/registry/consul/v2(API 兼容) - Go module:yz-svr → git.5iot.com/iotlcm-public/iotlcm-upstream-proxy - 凭据与内部标识脱敏:MySQL/Redis/Consul 凭据、私网与生产 IP、内网域名 - 移除内部资产:CI 触发器 - Dockerfile 改用公共基础镜像,移除写死的 registry 凭据 - 补 Apache-2.0 LICENSE,重写 README,LF 行尾 + .gitattributes
260 lines
7.8 KiB
Go
260 lines
7.8 KiB
Go
package channelconfig
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"strings"
|
|
|
|
"git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/model"
|
|
"git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/model/entity"
|
|
"git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/service"
|
|
|
|
"git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/dao"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/gogf/gf/v2/crypto/gmd5"
|
|
"github.com/gogf/gf/v2/encoding/gjson"
|
|
"github.com/gogf/gf/v2/errors/gerror"
|
|
"github.com/gogf/gf/v2/frame/g"
|
|
)
|
|
|
|
// channelConfigRow extends the generated entity without losing SQL NULL values.
|
|
type channelConfigRow struct {
|
|
entity.TSimChannels
|
|
IsNormal *int `json:"isNormal" orm:"is_normal"`
|
|
}
|
|
|
|
type sChannelConfig struct {
|
|
sync.RWMutex
|
|
channelCache map[string]*model.SimChannelConfigItemCache
|
|
loadVersion uint8
|
|
chgLogMaxId int64
|
|
running bool
|
|
}
|
|
|
|
func NewChannelConfig() *sChannelConfig {
|
|
return &sChannelConfig{
|
|
channelCache: make(map[string]*model.SimChannelConfigItemCache),
|
|
}
|
|
}
|
|
|
|
func init() {
|
|
service.RegisterChannelConfig(NewChannelConfig())
|
|
}
|
|
|
|
func (s *sChannelConfig) Start(ctx context.Context) error {
|
|
err := s.LoadAllChannelConfig(ctx)
|
|
if err != nil {
|
|
g.Log().Errorf(ctx, "ChannelConfig Start err %+v", err)
|
|
return err
|
|
}
|
|
s.running = true
|
|
err = s.Loop(ctx)
|
|
if err != nil {
|
|
g.Log().Errorf(ctx, "ChannelConfig Start err %+v", err)
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *sChannelConfig) Stop(ctx context.Context) error {
|
|
s.running = false
|
|
return nil
|
|
}
|
|
|
|
func (s *sChannelConfig) Loop(ctx context.Context) error {
|
|
g.Log().Infof(ctx, "ChannelConfig loop begin.")
|
|
lastLoadAllChannelConfigTime := int64(0)
|
|
go func() {
|
|
for s.running {
|
|
now := time.Now().Unix()
|
|
loadAllInterval := g.Cfg().MustGet(ctx, "channel.load_all_interval", 2*60*60).Int64()
|
|
if now-lastLoadAllChannelConfigTime >= loadAllInterval {
|
|
lastLoadAllChannelConfigTime = now
|
|
s.LoadAllChannelConfig(ctx)
|
|
}
|
|
time.Sleep(1 * time.Second)
|
|
}
|
|
}()
|
|
g.Log().Infof(ctx, "ChannelConfig loop background goroutine started.")
|
|
return nil
|
|
}
|
|
|
|
func (s *sChannelConfig) LoadAllChannelConfig(ctx context.Context) error {
|
|
s.loadVersion++
|
|
const warnRowThreshold = 1000
|
|
|
|
dbSimChannels := make([]*channelConfigRow, 0)
|
|
err := dao.TSimChannels.Ctx(ctx).
|
|
OrderAsc(dao.TSimChannels.Columns().Id).
|
|
Scan(&dbSimChannels)
|
|
if err != nil && err != sql.ErrNoRows {
|
|
return err
|
|
}
|
|
if len(dbSimChannels) >= warnRowThreshold {
|
|
g.Log().Warningf(ctx, "LoadAllChannelConfig TSimChannels row count %d >= %d, confirm small-table assumption or add pagination",
|
|
len(dbSimChannels), warnRowThreshold)
|
|
}
|
|
|
|
dbSimChannelTemplates := make([]*entity.TSimChannelTemplate, 0)
|
|
err = dao.TSimChannelTemplate.Ctx(ctx).
|
|
OrderAsc(dao.TSimChannelTemplate.Columns().Id).
|
|
Scan(&dbSimChannelTemplates)
|
|
if err != nil && err != sql.ErrNoRows {
|
|
return err
|
|
}
|
|
if len(dbSimChannelTemplates) >= warnRowThreshold {
|
|
g.Log().Warningf(ctx, "LoadAllChannelConfig TSimChannelTemplate row count %d >= %d, confirm small-table assumption or add pagination",
|
|
len(dbSimChannelTemplates), warnRowThreshold)
|
|
}
|
|
|
|
templateByKey := make(map[string]*entity.TSimChannelTemplate, len(dbSimChannelTemplates))
|
|
for _, tpl := range dbSimChannelTemplates {
|
|
if tpl != nil {
|
|
templateByKey[tpl.TempKey] = tpl
|
|
}
|
|
}
|
|
|
|
s.Lock()
|
|
for _, dbSimChannel := range dbSimChannels {
|
|
if dbSimChannel == nil {
|
|
continue
|
|
}
|
|
cache := &model.SimChannelConfigItemCache{
|
|
Channel: &dbSimChannel.TSimChannels,
|
|
IsNormal: dbSimChannel.IsNormal,
|
|
Version: s.getChanVersion(ctx, dbSimChannel),
|
|
}
|
|
oldChannelCache, _ := s.channelCache[dbSimChannel.ChanKey]
|
|
if cache.CanCreateInstance() {
|
|
template, ok := templateByKey[dbSimChannel.TempKey]
|
|
if !ok {
|
|
g.Log().Warningf(ctx, "LoadAllChannelConfig no template for tempKey=%s chanKey=%s", dbSimChannel.TempKey, dbSimChannel.ChanKey)
|
|
continue
|
|
}
|
|
cache.Template = template
|
|
cache.Version += s.getTemplateVersion(ctx, template)
|
|
}
|
|
|
|
if oldChannelCache != nil && oldChannelCache.Version == cache.Version {
|
|
continue
|
|
}
|
|
s.channelCache[dbSimChannel.ChanKey] = cache
|
|
}
|
|
s.Unlock()
|
|
return nil
|
|
}
|
|
|
|
func (s *sChannelConfig) RefreshChannel(ctx context.Context, chanKey string) error {
|
|
if chanKey == "" {
|
|
return gerror.New("chanKey is required")
|
|
}
|
|
channel := new(channelConfigRow)
|
|
if err := dao.TSimChannels.Ctx(ctx).Where(dao.TSimChannels.Columns().ChanKey, chanKey).Scan(channel); err != nil {
|
|
return err
|
|
}
|
|
if channel.ChanKey == "" {
|
|
return gerror.Newf("channel not found: %s", chanKey)
|
|
}
|
|
cache := &model.SimChannelConfigItemCache{
|
|
Channel: &channel.TSimChannels,
|
|
IsNormal: channel.IsNormal,
|
|
Version: s.getChanVersion(ctx, channel),
|
|
}
|
|
// Ineligible channels refresh successfully even while their template is incomplete.
|
|
if cache.CanCreateInstance() {
|
|
template := new(entity.TSimChannelTemplate)
|
|
if err := dao.TSimChannelTemplate.Ctx(ctx).Where(dao.TSimChannelTemplate.Columns().TempKey, channel.TempKey).Scan(template); err != nil {
|
|
return err
|
|
}
|
|
if template.TempKey == "" {
|
|
return gerror.Newf("channel template not found: %s", channel.TempKey)
|
|
}
|
|
cache.Template = template
|
|
cache.Version += s.getTemplateVersion(ctx, template)
|
|
}
|
|
s.Lock()
|
|
s.channelCache[chanKey] = cache
|
|
s.Unlock()
|
|
return nil
|
|
}
|
|
|
|
// RemoveChannel removes a channel configuration cache. Missing keys are
|
|
// intentionally treated as success so rename retries remain idempotent.
|
|
func (s *sChannelConfig) RemoveChannel(ctx context.Context, chanKey string) error {
|
|
chanKey = strings.TrimSpace(chanKey)
|
|
if chanKey == "" {
|
|
return gerror.New("chanKey is required")
|
|
}
|
|
s.Lock()
|
|
delete(s.channelCache, chanKey)
|
|
s.Unlock()
|
|
g.Log().Infof(ctx, "channel config removed, chanKey: %s", chanKey)
|
|
return nil
|
|
}
|
|
|
|
// get alarm cache by id
|
|
func (s *sChannelConfig) GetChannel(chanKey string) (*model.SimChannelConfigItemCache, bool) {
|
|
s.RLock()
|
|
defer s.RUnlock()
|
|
channelCache, ok := s.channelCache[chanKey]
|
|
return channelCache, ok
|
|
}
|
|
|
|
func (s *sChannelConfig) GetAllChannel(ctx context.Context) ([]*model.SimChannelConfigItemCache, error) {
|
|
s.RLock()
|
|
defer s.RUnlock()
|
|
channels := make([]*model.SimChannelConfigItemCache, 0)
|
|
for _, channelCache := range s.channelCache {
|
|
channels = append(channels, channelCache)
|
|
}
|
|
return channels, nil
|
|
}
|
|
|
|
func (s *sChannelConfig) getChanVersion(ctx context.Context, channel *channelConfigRow) string {
|
|
chanKey := channel.ChanKey
|
|
|
|
channelNew := &channelConfigRow{
|
|
IsNormal: channel.IsNormal,
|
|
TSimChannels: entity.TSimChannels{
|
|
TempKey: channel.TempKey,
|
|
ChanKey: channel.ChanKey,
|
|
ChanName: channel.ChanName,
|
|
ChanConfig: channel.ChanConfig,
|
|
ChanStatus: channel.ChanStatus,
|
|
ChanAlias: channel.ChanAlias,
|
|
RegionCode: channel.RegionCode,
|
|
AppKey: channel.AppKey,
|
|
AppSecret: channel.AppSecret,
|
|
AccessToken: channel.AccessToken,
|
|
RefreshToken: channel.RefreshToken,
|
|
},
|
|
}
|
|
chanVersion, err := gmd5.EncryptString(gjson.MustEncodeString(channelNew))
|
|
if err != nil {
|
|
g.Log().Errorf(ctx, "sChannelConfig.getChanVersion encrypt error %+v, chanKey %s", err, chanKey)
|
|
return ""
|
|
}
|
|
g.Log().Debugf(ctx, "sChannelConfig.getChanVersion chanVersion=%s chanKey=%s", chanVersion, chanKey)
|
|
return chanVersion
|
|
}
|
|
|
|
func (s *sChannelConfig) getTemplateVersion(ctx context.Context, template *entity.TSimChannelTemplate) string {
|
|
templateKey := template.TempKey
|
|
templateNew := &entity.TSimChannelTemplate{
|
|
TempKey: template.TempKey,
|
|
TempName: template.TempName,
|
|
TempModel: template.TempModel,
|
|
TempConfig: template.TempConfig,
|
|
TempStatus: template.TempStatus,
|
|
}
|
|
templateVersion, err := gmd5.EncryptString(gjson.MustEncodeString(templateNew))
|
|
if err != nil {
|
|
g.Log().Errorf(ctx, "sChannelConfig.getTemplateVersion encrypt error %+v, templateKey %s, templateNew %+v", err, templateKey, templateNew)
|
|
return ""
|
|
}
|
|
g.Log().Debugf(ctx, "sChannelConfig.getTemplateVersion templateVersion=%s templateKey=%s", templateVersion, templateKey)
|
|
return templateVersion
|
|
}
|