chore: 初始开源发布
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
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
package upstreamimpl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
func TestApikaProxyTemplateRegistration(t *testing.T) {
|
||||
template, ok := service.UpstreamAPI().GetTemp("sim_onelink_kedang_v1")
|
||||
if !ok || template == nil {
|
||||
t.Fatal("apika proxy template not registered")
|
||||
}
|
||||
config := &model.SimChannelConfigItemCache{
|
||||
Template: &entity.TSimChannelTemplate{TempKey: "sim_onelink_kedang_v1"},
|
||||
Channel: &entity.TSimChannels{ChanKey: "apika-test"},
|
||||
}
|
||||
if instance := template.NewTemp(context.Background(), config); instance == nil {
|
||||
t.Fatal("apika proxy template failed to instantiate")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package dx
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Config 网关鉴权与请求配置
|
||||
type Config struct {
|
||||
BaseURL string // 默认 https://cmp-api.ctwing.cn:20164
|
||||
AppKey string
|
||||
AppSecret string
|
||||
Timeout time.Duration
|
||||
}
|
||||
|
||||
// Client API 客户端
|
||||
type Client struct {
|
||||
baseURL string
|
||||
appKey string
|
||||
appSecret string
|
||||
http *http.Client
|
||||
}
|
||||
|
||||
// New 创建客户端
|
||||
func NewClient(cfg Config) *Client {
|
||||
base := cfg.BaseURL
|
||||
if base == "" {
|
||||
base = DefaultBaseURL
|
||||
}
|
||||
timeout := cfg.Timeout
|
||||
if timeout == 0 {
|
||||
timeout = 10 * time.Second
|
||||
}
|
||||
return &Client{
|
||||
baseURL: strings.TrimSuffix(base, "/"),
|
||||
appKey: cfg.AppKey,
|
||||
appSecret: cfg.AppSecret,
|
||||
http: &http.Client{
|
||||
Timeout: timeout,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// DoGetRequest 通用GET请求(自动处理签名)
|
||||
// endpoint: API路径,如 "/api/v1/openbill/queryTraffic"
|
||||
// params: 查询参数,需调用方自行构建
|
||||
func (c *Client) DoGetRequest(ctx context.Context, endpoint string, params url.Values) ([]byte, error) {
|
||||
req, err := c.doGetRequest(ctx, endpoint, params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.doRequest(req)
|
||||
}
|
||||
|
||||
// doGetRequest 封装通用的 GET 请求构建逻辑
|
||||
func (c *Client) doGetRequest(ctx context.Context, endpoint string, params url.Values) (*http.Request, error) {
|
||||
queryString := params.Encode()
|
||||
timestamp := GetTimestampGMT()
|
||||
signStr, err := BuildSign(queryString, "", c.appSecret, timestamp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reqURL := c.baseURL + endpoint + "?" + queryString
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, reqURL, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("AppKey", c.appKey)
|
||||
req.Header.Set("Sign", signStr)
|
||||
req.Header.Set("Timestamp", timestamp)
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// doRequest 执行HTTP请求
|
||||
func (c *Client) doRequest(req *http.Request) ([]byte, error) {
|
||||
resp, err := c.http.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("http status %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
return body, nil
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package dx
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultBaseURL 电信API基础地址
|
||||
DefaultBaseURL = "https://cmp-api.ctwing.cn:20164"
|
||||
)
|
||||
|
||||
// GetTimestampGMT 返回 GMT 时区的 yyyyMMddHHmmss 格式时间戳
|
||||
func GetTimestampGMT() string {
|
||||
return time.Now().UTC().Format("20060102150405")
|
||||
}
|
||||
|
||||
// OrderParams 将 URL params 按 key 字典序排序
|
||||
// 输入如 "b=1&c=2&a=1",输出 "a=1&b=1&c=2"
|
||||
func OrderParams(params string) (string, error) {
|
||||
if params == "" {
|
||||
return "", nil
|
||||
}
|
||||
kvs := strings.Split(params, "&")
|
||||
m := make(map[string]string, len(kvs))
|
||||
for _, kv := range kvs {
|
||||
idx := strings.Index(kv, "=")
|
||||
if idx < 0 {
|
||||
continue
|
||||
}
|
||||
m[kv[:idx]] = kv[idx+1:]
|
||||
}
|
||||
keys := make([]string, 0, len(m))
|
||||
for k := range m {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
var parts []string
|
||||
for _, k := range keys {
|
||||
parts = append(parts, k+"="+m[k])
|
||||
}
|
||||
return strings.Join(parts, "&"), nil
|
||||
}
|
||||
|
||||
// SignMD5 对 reqStr 做 MD5 加密,返回小写十六进制字符串
|
||||
func SignMD5(reqStr string) string {
|
||||
h := md5.Sum([]byte(reqStr))
|
||||
return strings.ToLower(hex.EncodeToString(h[:]))
|
||||
}
|
||||
|
||||
// BuildSign 构造签名:params 排序后拼接 params+body+secretKey+timestamp 再 MD5
|
||||
// 注意:与参考项目一致,对 MD5 结果再做一次 hex 编码
|
||||
func BuildSign(params, body, secretKey, timestamp string) (string, error) {
|
||||
ordered, err := OrderParams(params)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
reqStr := ordered + body + secretKey + timestamp
|
||||
md5Str := SignMD5(reqStr)
|
||||
// 对 MD5 结果再做一次 hex 编码(与参考项目一致)
|
||||
hexStr := hex.EncodeToString([]byte(md5Str))
|
||||
return hexStr, nil
|
||||
}
|
||||
|
||||
// BuildSignWithDebug 构造签名并输出调试信息
|
||||
func BuildSignWithDebug(ctx context.Context, params, body, secretKey, timestamp, appKey string) (string, error) {
|
||||
ordered, err := OrderParams(params)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
reqStr := ordered + body + secretKey + timestamp
|
||||
|
||||
maskedSecret := secretKey
|
||||
|
||||
g.Log().Debugf(ctx, "[签名调试] AppKey: %s", appKey)
|
||||
g.Log().Debugf(ctx, "[签名调试] SecretKey: %s", maskedSecret)
|
||||
g.Log().Debugf(ctx, "[签名调试] Timestamp: %s", timestamp)
|
||||
g.Log().Debugf(ctx, "[签名调试] 排序后参数: %s", ordered)
|
||||
g.Log().Debugf(ctx, "[签名调试] Body: '%s'", body)
|
||||
g.Log().Debugf(ctx, "[签名调试] 签名原串: %s%s[SecretKey]%s", ordered, body, timestamp)
|
||||
|
||||
md5Str := SignMD5(reqStr)
|
||||
g.Log().Debugf(ctx, "[签名调试] MD5结果: %s", md5Str)
|
||||
|
||||
// 对 MD5 结果再做一次 hex 编码
|
||||
hexStr := hex.EncodeToString([]byte(md5Str))
|
||||
g.Log().Debugf(ctx, "[签名调试] 最终签名(hex编码后): %s", hexStr)
|
||||
|
||||
return hexStr, nil
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package upstreamimpl
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/model"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/net/gclient"
|
||||
)
|
||||
|
||||
func readGatewayResponse(response *gclient.Response) (model.SimBaseResJson, error) {
|
||||
defer response.Close()
|
||||
body := response.ReadAll()
|
||||
var output struct {
|
||||
Code *int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Data model.SimBaseResJson `json:"data"`
|
||||
}
|
||||
if err := json.Unmarshal(body, &output); err != nil {
|
||||
return nil, gerror.Wrap(err, "解析网关响应失败")
|
||||
}
|
||||
if response.StatusCode < 200 || response.StatusCode >= 300 || output.Code == nil || *output.Code != 0 {
|
||||
message := strings.TrimSpace(output.Message)
|
||||
if message == "" {
|
||||
message = fmt.Sprintf("网关响应无效,HTTP状态码:%d", response.StatusCode)
|
||||
}
|
||||
return nil, gerror.New(message)
|
||||
}
|
||||
return output.Data, nil
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package upstreamimpl_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
_ "git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/logic"
|
||||
"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"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
"github.com/gogf/gf/v2/os/gcfg"
|
||||
)
|
||||
|
||||
func TestMingqianProxyTemplate(t *testing.T) {
|
||||
template, ok := service.UpstreamAPI().GetTemp("sim_mingqian_v3")
|
||||
if !ok || template == nil {
|
||||
t.Fatal("mingqian proxy template missing")
|
||||
}
|
||||
instance := template.NewTemp(context.Background(), &model.SimChannelConfigItemCache{Template: &entity.TSimChannelTemplate{TempKey: "sim_mingqian_v3"}, Channel: &entity.TSimChannels{ChanKey: "mingqian-test"}})
|
||||
if instance == nil {
|
||||
t.Fatal("mingqian proxy instance missing")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMingqianProxyPreservesFlowFields(t *testing.T) {
|
||||
const response = `{"code":0,"data":[{"code":0,"message":"","simKey":{"chanKey":"mingqian-test","iccid":"A"},"resData":{"dataUsage":1,"packageFlow":{"totalFlow":10240,"dataUsage":8192,"remainingFlow":2048}}},{"code":0,"message":"package flow unavailable","simKey":{"chanKey":"mingqian-test","iccid":"B"},"resData":{"dataUsage":2,"packageFlowError":"mingqian: business code 500"}},{"code":1,"message":"monthly usage unavailable","simKey":{"chanKey":"mingqian-test","iccid":"C"},"resData":{"dataUsage":0,"packageFlow":{"totalFlow":10,"dataUsage":8,"remainingFlow":2}}}]}`
|
||||
server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
||||
if request.Method != http.MethodPost || request.URL.Path != "/sim/query/sim-datausage" {
|
||||
t.Errorf("wrong gateway route: %s %s", request.Method, request.URL.Path)
|
||||
}
|
||||
var input model.SimBaseReqJson
|
||||
if err := request.ParseForm(); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if err := json.Unmarshal([]byte(request.Form.Get("simKeys")), &input.SimKeys); err != nil || len(input.SimKeys) != 3 || input.SimKeys[0].ICCID != "A" {
|
||||
t.Errorf("request changed: %+v %v", input, err)
|
||||
}
|
||||
writer.Header().Set("Content-Type", "application/json")
|
||||
fmt.Fprint(writer, response)
|
||||
}))
|
||||
t.Cleanup(server.Close)
|
||||
config, err := gcfg.NewAdapterContent(fmt.Sprintf(`{"gateway":{"address":%q,"timeout":"1s"}}`, strings.TrimPrefix(server.URL, "http://")))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
original := g.Cfg().GetAdapter()
|
||||
g.Cfg().SetAdapter(config)
|
||||
t.Cleanup(func() { g.Cfg().SetAdapter(original) })
|
||||
template, ok := service.UpstreamAPI().GetTemp("sim_mingqian_v3")
|
||||
if !ok {
|
||||
t.Fatal("missing Mingqian registration")
|
||||
}
|
||||
instance := template.NewTemp(context.Background(), &model.SimChannelConfigItemCache{Template: &entity.TSimChannelTemplate{TempKey: "sim_mingqian_v3"}, Channel: &entity.TSimChannels{ChanKey: "mingqian-test"}})
|
||||
request := &ghttp.Request{Request: httptest.NewRequest(http.MethodPost, "/sim/query/sim-datausage", nil)}
|
||||
output, err := instance.SimBase(request.Context(), &model.SimBaseReqJson{
|
||||
SimKeys: []*model.SimKey{{ChanKey: "mingqian-test", ICCID: "A"}, {ChanKey: "mingqian-test", ICCID: "B"}, {ChanKey: "mingqian-test", ICCID: "C"}},
|
||||
ReqData: json.RawMessage(`{}`),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var expected model.SimResponseData[model.SimBaseResJson]
|
||||
if err := json.Unmarshal([]byte(response), &expected); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(output, expected.Data) {
|
||||
t.Fatalf("proxy dropped package fields or changed failure status: %+v", output)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package upstreamimpl_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
_ "git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/logic"
|
||||
"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"
|
||||
)
|
||||
|
||||
func TestMobilePublicProxyTemplateRegistration(t *testing.T) {
|
||||
for _, templateKey := range []string{
|
||||
"sim_onelink_daliuliang_v1",
|
||||
"sim__daliuliang_v1",
|
||||
"sim_onelink_mobile_public_v1",
|
||||
} {
|
||||
t.Run(templateKey, func(t *testing.T) {
|
||||
template, ok := service.UpstreamAPI().GetTemp(templateKey)
|
||||
if !ok || template == nil {
|
||||
t.Fatal("mobile public proxy template not registered")
|
||||
}
|
||||
config := &model.SimChannelConfigItemCache{
|
||||
Template: &entity.TSimChannelTemplate{TempKey: templateKey},
|
||||
Channel: &entity.TSimChannels{ChanKey: "daliuliang-test"},
|
||||
}
|
||||
if instance := template.NewTemp(context.Background(), config); instance == nil {
|
||||
t.Fatal("mobile public proxy failed to instantiate")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package upstreamimpl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/logic/upstreamimpl/dx"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/model"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/service"
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/net/gsvc"
|
||||
)
|
||||
|
||||
// XML命名空间正则
|
||||
var xmlNamespaceRegex = regexp.MustCompile(`xmlns[^=]*="[^"]*"\s*|:web:`)
|
||||
|
||||
type sSimDX struct {
|
||||
channelConfig *model.SimChannelConfigItemCache
|
||||
client *dx.Client
|
||||
}
|
||||
|
||||
func NewSimDX() *sSimDX {
|
||||
return &sSimDX{}
|
||||
}
|
||||
|
||||
func init() {
|
||||
s := NewSimDX()
|
||||
service.UpstreamAPI().RegisterTemp("sim_dx_v1", s)
|
||||
service.UpstreamAPI().RegisterTemp("sim_dx_v3", s)
|
||||
service.RegisterSimDX(s)
|
||||
}
|
||||
|
||||
func (s *sSimDX) NewTemp(ctx context.Context, channelConfig *model.SimChannelConfigItemCache) model.SimCardChannelI {
|
||||
client := dx.NewClient(dx.Config{
|
||||
BaseURL: dx.DefaultBaseURL,
|
||||
AppKey: channelConfig.Channel.AppKey,
|
||||
AppSecret: channelConfig.Channel.AppSecret,
|
||||
})
|
||||
return &sSimDX{
|
||||
channelConfig: channelConfig,
|
||||
client: client,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *sSimDX) getUpstreamUrl(ctx context.Context, serviceUrl string) (string, error) {
|
||||
address, err := g.Cfg().Get(ctx, "gateway.address")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("http://%s%s", address, serviceUrl), nil
|
||||
// return fmt.Sprintf("http://%s%s", "127.0.0.1:8110", serviceUrl), nil
|
||||
|
||||
}
|
||||
|
||||
func (s *sSimDX) getUpstreamTimeout(ctx context.Context) time.Duration {
|
||||
timeout, _ := g.Cfg().Get(ctx, "gateway.timeout")
|
||||
if timeout == nil {
|
||||
return 10 * time.Second
|
||||
}
|
||||
if d := timeout.Duration(); d > 0 {
|
||||
return d
|
||||
}
|
||||
if seconds := timeout.Int(); seconds > 0 {
|
||||
return time.Duration(seconds) * time.Second
|
||||
}
|
||||
return 10 * time.Second
|
||||
}
|
||||
|
||||
func (s *sSimDX) SimBase(ctx context.Context, input *model.SimBaseReqJson) (model.SimBaseResJson, error) {
|
||||
r := g.RequestFromCtx(ctx)
|
||||
if r.Method != "POST" {
|
||||
return nil, gerror.New("method not allowed")
|
||||
}
|
||||
path := r.URL.Path
|
||||
if path == "" {
|
||||
return nil, gerror.New("path is required")
|
||||
}
|
||||
url, err := s.getUpstreamUrl(ctx, path)
|
||||
if err != nil {
|
||||
g.Log().Debugf(ctx, "SimBase getUpstreamUrl error %+v, ", err)
|
||||
return nil, err
|
||||
}
|
||||
client := g.Client()
|
||||
client.SetDiscovery(gsvc.GetRegistry())
|
||||
client.SetTimeout(s.getUpstreamTimeout(ctx) * time.Duration(len(input.SimKeys)))
|
||||
resp, err := client.Post(ctx, url, input)
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "SimBase post error %+v,url %s, params %+v, ", err, url, input)
|
||||
return nil, err
|
||||
}
|
||||
return readGatewayResponse(resp)
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package upstreamimpl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/model"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/service"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/net/gsvc"
|
||||
"golang.org/x/sync/singleflight"
|
||||
)
|
||||
|
||||
const (
|
||||
gUrlBase = "https://api.iot.10086.cn/v5/"
|
||||
)
|
||||
|
||||
type sSimYD struct {
|
||||
channelConfig *model.SimChannelConfigItemCache
|
||||
seqId atomic.Int64
|
||||
tokenFlight singleflight.Group
|
||||
}
|
||||
|
||||
func NewSimYD() *sSimYD {
|
||||
return &sSimYD{}
|
||||
}
|
||||
|
||||
func init() {
|
||||
s := NewSimYD()
|
||||
service.UpstreamAPI().RegisterTemp("sim_yd_v5", s)
|
||||
service.UpstreamAPI().RegisterTemp("sim_yd_mock_v5", s)
|
||||
// These channel implementations live in the upstream gateway. The proxy
|
||||
// forwards their API calls through the same generic gateway route, so any
|
||||
// template registered in iotlcm-upstream-chan must be registered here too:
|
||||
// channel refresh fails with "channel template not registered" otherwise.
|
||||
// sSimYD has no template-specific logic, so an alias per template suffices.
|
||||
service.UpstreamAPI().RegisterTemp("sim_sdtywl_v1", s)
|
||||
service.UpstreamAPI().RegisterTemp("sim_onelink_tengyu_v1", s)
|
||||
service.UpstreamAPI().RegisterTemp("sim_onelink_xuzhou_mobile_v1", s)
|
||||
service.UpstreamAPI().RegisterTemp("sim_onelink_lelian_v1", s)
|
||||
service.UpstreamAPI().RegisterTemp("sim_onelink_qianxun_v1", s)
|
||||
service.UpstreamAPI().RegisterTemp("sim_onelink_henan_xinjing_v1", s)
|
||||
service.UpstreamAPI().RegisterTemp("sim_henan_xinjing_direct_v1", s)
|
||||
service.UpstreamAPI().RegisterTemp("sim_onelink_zibo_v1", s)
|
||||
service.UpstreamAPI().RegisterTemp("sim_tibiot_v1", s)
|
||||
service.UpstreamAPI().RegisterTemp("sim_qianxun_v3", s)
|
||||
service.UpstreamAPI().RegisterTemp("sim_yunda_v1", s)
|
||||
service.UpstreamAPI().RegisterTemp("sim_mingqian_v3", s)
|
||||
service.UpstreamAPI().RegisterTemp("sim_daihe_v1", s)
|
||||
service.UpstreamAPI().RegisterTemp("sim_onelink_daihe_v1", s)
|
||||
service.UpstreamAPI().RegisterTemp("sim_onelink_daliuliang_v1", s)
|
||||
service.UpstreamAPI().RegisterTemp("sim__daliuliang_v1", s)
|
||||
service.UpstreamAPI().RegisterTemp("sim_onelink_mobile_public_v1", s)
|
||||
service.UpstreamAPI().RegisterTemp("sim_onelink_kedang_v1", s)
|
||||
service.UpstreamAPI().RegisterTemp("lanshang_8003", s)
|
||||
service.UpstreamAPI().RegisterTemp("lanshang_8000", s)
|
||||
service.UpstreamAPI().RegisterTemp("sim_onelink_yunze_v1", s)
|
||||
// 江苏倍信(jsbxkj)共享 Token。协议实现在 chan 的 beixin_token_provider.go;
|
||||
// proxy 只负责把业务 API 转发到 gateway,与腾宇/澜数同一形态。
|
||||
service.UpstreamAPI().RegisterTemp("sim_onelink_beixin_v1", s)
|
||||
service.UpstreamAPI().RegisterTemp("sim_onelink_lanshu_v1", s)
|
||||
service.RegisterSimYD(s)
|
||||
}
|
||||
|
||||
func (s *sSimYD) NewTemp(ctx context.Context, channelConfig *model.SimChannelConfigItemCache) model.SimCardChannelI {
|
||||
return &sSimYD{
|
||||
channelConfig: channelConfig,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *sSimYD) getUpstreamUrl(ctx context.Context, serviceUrl string) string {
|
||||
address, _ := g.Cfg().Get(ctx, "gateway.address")
|
||||
return fmt.Sprintf("http://%s%s", address, serviceUrl)
|
||||
// return fmt.Sprintf("http://%s%s", "127.0.0.1:8110", serviceUrl)
|
||||
}
|
||||
|
||||
func (s *sSimYD) getUpstreamTimeout(ctx context.Context) time.Duration {
|
||||
timeout, _ := g.Cfg().Get(ctx, "gateway.timeout")
|
||||
if timeout == nil {
|
||||
return 10 * time.Second
|
||||
}
|
||||
if d := timeout.Duration(); d > 0 {
|
||||
return d
|
||||
}
|
||||
if seconds := timeout.Int(); seconds > 0 {
|
||||
return time.Duration(seconds) * time.Second
|
||||
}
|
||||
return 10 * time.Second
|
||||
}
|
||||
|
||||
func (s *sSimYD) SimBase(ctx context.Context, input *model.SimBaseReqJson) (model.SimBaseResJson, error) {
|
||||
r := g.RequestFromCtx(ctx)
|
||||
if r.Method != "POST" {
|
||||
g.Log().Errorf(ctx, "SimYD.SimBase, method not allowed, method %s, input %+v", r.Method, input)
|
||||
return nil, gerror.New("方法调用错误")
|
||||
}
|
||||
path := r.URL.Path
|
||||
if path == "" {
|
||||
g.Log().Errorf(ctx, "SimYD.SimBase, path is required, input %+v", input)
|
||||
return nil, gerror.New("路径不能为空")
|
||||
}
|
||||
url := s.getUpstreamUrl(ctx, path)
|
||||
g.Log().Debugf(ctx, "SimYD.SimBase, url %s", url)
|
||||
client := g.Client()
|
||||
client.SetDiscovery(gsvc.GetRegistry())
|
||||
client.SetTimeout(s.getUpstreamTimeout(ctx) * time.Duration(len(input.SimKeys)))
|
||||
resp, err := client.Post(ctx, url, input)
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "SimYD.SimBase, post error %+v,url %s, params %+v, ", err, url, input)
|
||||
return nil, gerror.New("请求失败")
|
||||
}
|
||||
return readGatewayResponse(resp)
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package upstreamimpl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
// chanTemplateKeys lists every template iotlcm-upstream-chan registers in its
|
||||
// own upstreamimpl init() functions. Source of truth:
|
||||
//
|
||||
// iotlcm-upstream-chan/internal/logic/upstreamimpl/sim_onelink_shared.go:18-29 (shared token family)
|
||||
// iotlcm-upstream-chan/internal/logic/upstreamimpl/sim_daihe.go:17
|
||||
// iotlcm-upstream-chan/internal/logic/upstreamimpl/sim_henan_xinjing_direct.go:18
|
||||
// iotlcm-upstream-chan/internal/logic/upstreamimpl/sim_qianxun.go:25
|
||||
// iotlcm-upstream-chan/internal/logic/upstreamimpl/sim_yunda.go:19
|
||||
// iotlcm-upstream-chan/internal/logic/upstreamimpl/sim_recharge.go:19
|
||||
// iotlcm-upstream-chan/internal/logic/upstreamimpl/sim_yd.go (sim_yd_v5)
|
||||
// iotlcm-upstream-chan/internal/logic/upstreamimpl/sim_yd_mock.go:21
|
||||
// iotlcm-upstream-chan/internal/service/mingqian/adapter.go:16
|
||||
//
|
||||
// Keep this list in sync when a template is added on the chan side: a template
|
||||
// missing here is exactly the failure mode where channel refresh returns
|
||||
// "channel template not registered".
|
||||
var chanTemplateKeys = []string{
|
||||
// 共享 Token 族
|
||||
"sim_onelink_tengyu_v1",
|
||||
"sim_onelink_xuzhou_mobile_v1",
|
||||
"sim_onelink_lelian_v1",
|
||||
"sim_onelink_qianxun_v1",
|
||||
"sim_onelink_henan_xinjing_v1",
|
||||
"sim_onelink_daihe_v1",
|
||||
"sim_onelink_daliuliang_v1",
|
||||
"sim__daliuliang_v1",
|
||||
"sim_onelink_mobile_public_v1",
|
||||
"sim_onelink_kedang_v1",
|
||||
"sim_onelink_yunze_v1",
|
||||
"sim_onelink_lanshu_v1",
|
||||
"sim_onelink_beixin_v1",
|
||||
// 非共享 Token 族
|
||||
"sim_yd_v5",
|
||||
"sim_yd_mock_v5",
|
||||
"sim_sdtywl_v1",
|
||||
"sim_daihe_v1",
|
||||
"sim_henan_xinjing_direct_v1",
|
||||
"sim_qianxun_v3",
|
||||
"sim_yunda_v1",
|
||||
"sim_mingqian_v3",
|
||||
"sim_tibiot_v1",
|
||||
"sim_dx_v1",
|
||||
"sim_dx_v3",
|
||||
}
|
||||
|
||||
// TestUpstreamTemplateParity guards the proxy/gateway template registration
|
||||
// contract: the proxy only forwards to the gateway, so every template the
|
||||
// gateway registers must also be registered here or RefreshChannel fails before
|
||||
// any request is made.
|
||||
func TestUpstreamTemplateParity(t *testing.T) {
|
||||
for _, tempKey := range chanTemplateKeys {
|
||||
tempKey := tempKey
|
||||
t.Run(tempKey, func(t *testing.T) {
|
||||
temp, ok := service.UpstreamAPI().GetTemp(tempKey)
|
||||
if !ok || temp == nil {
|
||||
t.Fatalf("template not registered in proxy: %s", tempKey)
|
||||
}
|
||||
config := &model.SimChannelConfigItemCache{
|
||||
Template: &entity.TSimChannelTemplate{TempKey: tempKey},
|
||||
Channel: &entity.TSimChannels{ChanKey: "parity-test"},
|
||||
}
|
||||
if instance := temp.NewTemp(context.Background(), config); instance == nil {
|
||||
t.Fatalf("template failed to instantiate: %s", tempKey)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user