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,5 @@
|
||||
// =================================================================================
|
||||
// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish.
|
||||
// =================================================================================
|
||||
|
||||
package cardquery
|
||||
@@ -0,0 +1,79 @@
|
||||
package cardquery
|
||||
|
||||
import (
|
||||
"crypto/subtle"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/model"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/service"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
)
|
||||
|
||||
var appDiagnosisQueryPaths = map[string]bool{
|
||||
"/sim/query/sim-basic-info": true,
|
||||
"/sim/query/sim-status": true,
|
||||
"/sim/query/sim-session": true,
|
||||
"/sim/query/sim-apn-info": true,
|
||||
"/sim/query/sim-imei": true,
|
||||
"/sim/query/sim-device-status": true,
|
||||
"/sim/query/sim-card-bind-status": true,
|
||||
"/sim/query/sim-data-diagnosis": true,
|
||||
"/sim/query/sim-stop-reason": true,
|
||||
"/sim/query/sim-frozen-state": true,
|
||||
"/sim/query/sim-datausage": true,
|
||||
"/sim/query/sim-region-limit-status": true,
|
||||
}
|
||||
|
||||
// AppDiagnosisQuery bypasses the proxy's local card lookup only for verified
|
||||
// internal, read-only diagnosis requests. The existing /sim/* API is untouched.
|
||||
func AppDiagnosisQuery(r *ghttp.Request) {
|
||||
secret := strings.TrimSpace(g.Cfg().MustGet(r.Context(), "appDiagnosis.internalToken").String())
|
||||
provided := r.Header.Get("X-App-Diagnosis-Token")
|
||||
if secret == "" || subtle.ConstantTimeCompare([]byte(secret), []byte(provided)) != 1 {
|
||||
appDiagnosisReject(r, http.StatusForbidden, "内部查询未授权")
|
||||
return
|
||||
}
|
||||
path := strings.TrimPrefix(r.URL.Path, "/internal/app-diagnosis")
|
||||
if !appDiagnosisQueryPaths[path] {
|
||||
appDiagnosisReject(r, http.StatusNotFound, "诊断接口不存在")
|
||||
return
|
||||
}
|
||||
if len(r.GetBody()) > 64*1024 {
|
||||
appDiagnosisReject(r, http.StatusRequestEntityTooLarge, "请求内容过大")
|
||||
return
|
||||
}
|
||||
var input model.SimBaseReqJson
|
||||
if err := json.Unmarshal(r.GetBody(), &input); err != nil || len(input.SimKeys) != 1 || input.SimKeys[0] == nil {
|
||||
appDiagnosisReject(r, http.StatusBadRequest, "诊断卡参数无效")
|
||||
return
|
||||
}
|
||||
key := input.SimKeys[0]
|
||||
key.ChanKey = strings.TrimSpace(key.ChanKey)
|
||||
if key.ChanKey == "" || strings.TrimSpace(key.ICCID) == "" {
|
||||
appDiagnosisReject(r, http.StatusBadRequest, "诊断卡通道或ICCID缺失")
|
||||
return
|
||||
}
|
||||
// Channel credentials and gateway routing are loaded by UpstreamAPI from
|
||||
// this proxy's own configured channel, never from the legacy database.
|
||||
channel, ok := service.UpstreamAPI().GetChan(key.ChanKey)
|
||||
if !ok || channel == nil || channel.Instance == nil {
|
||||
appDiagnosisReject(r, http.StatusNotFound, "诊断通道不可用")
|
||||
return
|
||||
}
|
||||
r.URL.Path = path
|
||||
data, err := service.UpstreamAPI().SimBase(r.Context(), &input)
|
||||
if err != nil {
|
||||
r.SetError(err)
|
||||
return
|
||||
}
|
||||
r.Response.WriteJson(g.Map{"code": 0, "data": data})
|
||||
}
|
||||
|
||||
func appDiagnosisReject(r *ghttp.Request, status int, message string) {
|
||||
r.Response.WriteStatus(status)
|
||||
r.Response.WriteJson(g.Map{"code": status, "message": message})
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package cardquery
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
"github.com/gogf/gf/v2/os/gcfg"
|
||||
)
|
||||
|
||||
func TestDiagnosisProxyRejectsUntrustedAndInvalidRequests(t *testing.T) {
|
||||
adapter, err := gcfg.NewAdapterContent(`{"appDiagnosis":{"internalToken":"test-token"}}`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
previous := g.Cfg().GetAdapter()
|
||||
g.Cfg().SetAdapter(adapter)
|
||||
t.Cleanup(func() { g.Cfg().SetAdapter(previous) })
|
||||
server := ghttp.GetServer("diagnosis-proxy-auth-test")
|
||||
server.SetAddr("127.0.0.1:0")
|
||||
server.Group("/", func(group *ghttp.RouterGroup) {
|
||||
group.POST("/internal/app-diagnosis/sim/*any", AppDiagnosisQuery)
|
||||
})
|
||||
if err := server.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(func() { _ = server.Shutdown() })
|
||||
url := "http://" + server.GetListenedAddress() + "/internal/app-diagnosis"
|
||||
for _, tc := range []struct {
|
||||
path string
|
||||
token string
|
||||
body string
|
||||
status int
|
||||
}{
|
||||
{"/sim/query/sim-basic-info", "", `{"simKeys":[{"iccid":"123","chanKey":"test"}]}`, http.StatusForbidden},
|
||||
{"/sim/restart", "test-token", `{"simKeys":[{"iccid":"123","chanKey":"test"}]}`, http.StatusNotFound},
|
||||
{"/sim/query/sim-basic-info", "test-token", `{"simKeys":[{"iccid":"123"}]}`, http.StatusBadRequest},
|
||||
} {
|
||||
request, err := http.NewRequest(http.MethodPost, url+tc.path, strings.NewReader(tc.body))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
request.Header.Set("X-App-Diagnosis-Token", tc.token)
|
||||
response, err := http.DefaultClient.Do(request)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_ = response.Body.Close()
|
||||
if response.StatusCode != tc.status {
|
||||
t.Errorf("%s: got %d want %d", tc.path, response.StatusCode, tc.status)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// =================================================================================
|
||||
// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish.
|
||||
// =================================================================================
|
||||
|
||||
package cardquery
|
||||
|
||||
import (
|
||||
"git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/api/cardquery"
|
||||
)
|
||||
|
||||
type ControllerV1 struct{}
|
||||
|
||||
func NewV1() cardquery.ICardqueryV1 {
|
||||
return &ControllerV1{}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package cardquery
|
||||
|
||||
import (
|
||||
"context"
|
||||
v1 "git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/api/cardquery/v1"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/service"
|
||||
)
|
||||
|
||||
// func (c *ControllerV1) SimBase(ctx context.Context, req *v1.SimBaseReq) (res *v1.SimBaseRes, err error) {
|
||||
// g.Log().Debugf(ctx, "SimBase req %+v", req)
|
||||
// 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")
|
||||
// }
|
||||
// // 获取渠道KEY
|
||||
// for _, simKey := range req.SimKeys {
|
||||
// if simKey.ChanKey == "" {
|
||||
// simKey.ChanKey = "channel_003"
|
||||
// }
|
||||
// }
|
||||
// // 获取三码信息
|
||||
|
||||
// url := "http://iotlcmupstreamproxy-http-svc" + path
|
||||
// client := g.Client()
|
||||
// client.SetDiscovery(gsvc.GetRegistry())
|
||||
// client.SetTimeout(time.Second * 10)
|
||||
// postData, err := json.Marshal(req)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// resp, err := client.Post(ctx, url, postData)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// body := resp.ReadAllString()
|
||||
// var simBaseRes model.SimBaseResJson
|
||||
// err = json.Unmarshal([]byte(body), &simBaseRes)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// return &simBaseRes, nil
|
||||
// }
|
||||
|
||||
func (c *ControllerV1) SimBase(ctx context.Context, req *v1.SimBaseReq) (res *v1.SimBaseRes, err error) {
|
||||
out, err := service.SimKeyFill().SimKeyFill(ctx, &req.SimBaseReqJson)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package channel
|
||||
|
||||
import "git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/api/channel"
|
||||
|
||||
type ControllerV1 struct{}
|
||||
|
||||
func NewV1() channel.IChannelV1 {
|
||||
return &ControllerV1{}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package channel
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
v1 "git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/api/channel/v1"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/service"
|
||||
)
|
||||
|
||||
func (c *ControllerV1) Refresh(ctx context.Context, req *v1.RefreshReq) (res *v1.RefreshRes, err error) {
|
||||
if err = service.UpstreamAPI().RefreshChannel(ctx, req.ChanKey); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &v1.RefreshRes{
|
||||
ChanKey: req.ChanKey,
|
||||
Refreshed: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *ControllerV1) Remove(ctx context.Context, req *v1.RemoveReq) (res *v1.RemoveRes, err error) {
|
||||
if err = service.UpstreamAPI().RemoveChannel(ctx, req.ChanKey); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &v1.RemoveRes{ChanKey: req.ChanKey, Removed: true}, nil
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package infopb
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/pb"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/pub/xjpubservice"
|
||||
|
||||
"github.com/gogf/gf/contrib/rpc/grpcx/v2"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
type Controller struct {
|
||||
pb.UnimplementedInfoServer
|
||||
}
|
||||
|
||||
func Register(s *grpcx.GrpcServer) {
|
||||
pb.RegisterInfoServer(s.Server, &Controller{})
|
||||
}
|
||||
|
||||
func (*Controller) Info(ctx context.Context, req *pb.InfoReq) (*pb.InfoRes, error) {
|
||||
g.Log().Info(ctx, req)
|
||||
info := xjpubservice.InfoRpc().GetInfo()
|
||||
return &pb.InfoRes{
|
||||
Head: &pb.HeadRes{
|
||||
Code: 0,
|
||||
Message: "success",
|
||||
},
|
||||
Type: info.Type,
|
||||
Name: info.Name,
|
||||
Title: info.Title,
|
||||
Description: info.Description,
|
||||
Version: info.Version,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package msgcallpb
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/pb"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/pub/xjpubservice"
|
||||
"github.com/gogf/gf/contrib/rpc/grpcx/v2"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
type Controller struct {
|
||||
pb.UnimplementedMsgCallServer
|
||||
}
|
||||
|
||||
func Register(s *grpcx.GrpcServer) {
|
||||
pb.RegisterMsgCallServer(s.Server, &Controller{})
|
||||
}
|
||||
|
||||
func (c *Controller) OneCall(ctx context.Context, req *pb.MsgReq) (res *pb.MsgRes, err error) {
|
||||
res = &pb.MsgRes{
|
||||
Head: &pb.HeadRes{
|
||||
Code: -1,
|
||||
Message: "service not found",
|
||||
},
|
||||
Service: req.Service,
|
||||
Method: req.Method,
|
||||
}
|
||||
if req.Head != nil {
|
||||
res.Head.RouterId = req.Head.RouterId
|
||||
res.Head.TraceId = req.Head.TraceId
|
||||
}
|
||||
|
||||
info := xjpubservice.InfoRpc().GetInfo()
|
||||
if req.Service != info.Name {
|
||||
g.Log().Errorf(ctx, "OneCall Service not found: service: %s, info: %s", req.Service, info.Name)
|
||||
return res, nil
|
||||
}
|
||||
{
|
||||
g.Log().Errorf(ctx, "OneCall Method not found: method: %s", req.Method)
|
||||
res.Head.Code = -1
|
||||
res.Head.Message = "method not found"
|
||||
return res, nil
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
Reference in New Issue
Block a user