chore: 并入 iotlcm-upstream-proxy 历史到 upstream-proxy/(原提交 44209ec)

This commit is contained in:
MiFaZhan
2026-11-02 13:28:02 +08:00
117 changed files with 8690 additions and 0 deletions
+14
View File
@@ -0,0 +1,14 @@
# 统一使用 LF,避免跨平台协作时产生整文件 diff
* text=auto eol=lf
# 二进制文件不做行尾转换
*.png binary
*.jpg binary
*.gif binary
*.ico binary
*.woff binary
*.woff2 binary
*.ttf binary
*.pdf binary
*.exe binary
*.go linguist-language=Go
@@ -0,0 +1,64 @@
name: mirror-to-gitee
# 把本仓同步到 Gitee 企业仓 snowgoat/<同名仓库>。
# 触发:main 分支推送、v* tag 推送、手动;另有每日兜底重试。
on:
push:
branches:
- main
tags:
- 'v*'
schedule:
# Gitea Actions 的 cron 用 UTC:18:30 UTC = 次日 02:30 北京时间
- cron: '30 18 * * *'
workflow_dispatch:
concurrency:
group: mirror-${{ github.ref }}
cancel-in-progress: true
jobs:
mirror:
runs-on: host
steps:
- name: 同步到 Gitee
env:
SRC_TOKEN: ${{ secrets.SRC_GIT_TOKEN }}
DST_TOKEN: ${{ secrets.GITEE_TOKEN }}
DST_OWNER: ${{ secrets.GITEE_OWNER }}
DST_USER: ${{ secrets.GITEE_USER }}
REPO_NAME: ${{ github.event.repository.name }}
run: |
set -u
if [ -z "${REPO_NAME:-}" ]; then
echo "未取到仓库名,退出"; exit 1
fi
WORK=$(mktemp -d)
trap 'rm -rf "$WORK"' EXIT
echo "源:git.5iot.com/iotlcm-public/$REPO_NAME"
echo "目标:gitee.com/$DST_OWNER/$REPO_NAME"
git clone --mirror "https://jenkins:${SRC_TOKEN}@git.5iot.com/iotlcm-public/${REPO_NAME}.git" "$WORK/repo.git"
cd "$WORK/repo.git"
echo "本地 ref:$(git for-each-ref --format='%(refname:short)' | tr '\n' ' ')"
# Gitee 在推送密集时会返回 429 Pending queue overflowed / 408 Authorization server timeout,
# 属服务端限流,退避重试即可。
attempt=0
while [ "$attempt" -lt 5 ]; do
attempt=$((attempt + 1))
echo "----- 推送尝试 $attempt/5"
if git push --mirror "https://${DST_USER}:${DST_TOKEN}@gitee.com/${DST_OWNER}/${REPO_NAME}.git" 2>&1; then
echo "✅ 已同步到 Gitee"
exit 0
fi
wait_s=$((attempt * 30))
echo "推送失败,${wait_s}s 后重试"
sleep "$wait_s"
done
echo "❌ 连续 5 次推送失败,请检查 Gitee 状态或令牌"
exit 1
@@ -0,0 +1,80 @@
name: trigger-jenkins-oss
# 打 tag 时触发 Jenkins 构建开源镜像并推送到镜像仓库。
# 仅在 tag 推送时触发;main 分支的日常提交不触发。
on:
push:
tags:
- 'v*'
workflow_dispatch:
# 同一 ref 连续触发时取消旧任务,只跑最新的
concurrency:
group: ${{ github.ref }}
cancel-in-progress: true
jobs:
trigger:
runs-on: host
steps:
- name: 触发 Jenkins 镜像构建
env:
JU: ${{ secrets.JENKINS_USER }}
JT: ${{ secrets.JENKINS_API_TOKEN }}
JURL: ${{ secrets.JENKINS_URL }}
JOB_PATH: ${{ secrets.JENKINS_JOB_PATH }}
REPO_NAME: ${{ github.event.repository.name }}
TAG_NAME: ${{ github.ref_name }}
run: |
set -eu
# 只有 tag 推送才构建;手动触发时按 tag 取,没有就退出
case "${GITHUB_REF:-}" in
refs/tags/v*) ;;
*)
if [ "${GITHUB_EVENT_NAME:-}" != "workflow_dispatch" ]; then
echo "非 tag 推送,跳过:${GITHUB_REF:-unknown}"
exit 0
fi
;;
esac
if [ -z "${TAG_NAME:-}" ]; then
echo "未取到 tag 名,退出"
exit 1
fi
# job 在 Jenkins 文件夹下的路径(组织密钥 JENKINS_JOB_PATH),
# 例如 job/iotlcm-opensrc/job/iotlcm-oss-build。
# 用密钥而非硬编码,Jenkins 里移动 job 时不用改 11 个仓。
JP="${JOB_PATH:-job/iotlcm-oss-build}"
echo "仓库:$REPO_NAME 版本:$TAG_NAME job:$JP"
WORK=$(mktemp -d)
trap 'rm -rf "$WORK"' EXIT
# 取 CSRF crumb
curl -s -c "$WORK/cj" -u "$JU:$JT" "$JURL/crumbIssuer/api/json" -o "$WORK/crumb.json"
CR=$(python3 -c "import json;print(json.load(open('$WORK/crumb.json'))['crumb'])")
# 触发参数化任务:SERVICES=仓库名,VERSION=tag
code=$(curl -s -o "$WORK/out" -w '%{http_code}' -X POST -b "$WORK/cj" -u "$JU:$JT" \
-H "Jenkins-Crumb: $CR" \
--data-urlencode "SERVICES=$REPO_NAME" \
--data-urlencode "VERSION=$TAG_NAME" \
--data-urlencode "FORCE=true" \
"$JURL/$JP/buildWithParameters")
echo "Jenkins 触发 HTTP $code"
if [ "$code" != "201" ] && [ "$code" != "200" ] && [ "$code" != "302" ] && [ "$code" != "303" ]; then
echo "触发失败:"
cat "$WORK/out"
exit 1
fi
LINK="$JURL/$JP/"
echo "=========================================="
echo "Jenkins 构建:$LINK"
echo "=========================================="
echo "::notice title=Jenkins 镜像构建::[$LINK]($LINK)"
+21
View File
@@ -0,0 +1,21 @@
.buildpath
.hgignore.swp
.project
.orig
.swp
.idea/
.settings/
.vscode/
bin/
**/.DS_Store
gf
main
*.exe
output/
manifest/output/
temp/
temp.yaml
bin
*.log
.keep-if-necessary
.gitkeep
+201
View File
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+7
View File
@@ -0,0 +1,7 @@
ROOT_DIR = $(shell pwd)
NAMESPACE = "default"
DEPLOY_NAME = "template-single"
DOCKER_NAME = "template-single"
include ./hack/hack-cli.mk
include ./hack/hack.mk
+21
View File
@@ -0,0 +1,21 @@
# iotlcm-upstream-proxy
配置示例见 `build/config.example.yaml` 与 `config/config.example.yaml`。请使用获授权的测试卡。
## 本地测试
```
curl -d '{"simKeys":[
{"chanKey":"12234",
"iccid":"23434",
"imsi":"222",
"msisdn":"2222"}
], "reqData":{"a":234}}' 127.0.0.1:8510/sim/adfds
curl -d '{"simKeys":[
{"chanKey":"",
"iccid":"89860000000000000001",
"imsi":"",
"msisdn":""}
], "reqData":{"yearMonth":"202602"}}' 127.0.0.1:8510/sim/query/sim-datausage-monthly
```
+15
View File
@@ -0,0 +1,15 @@
// =================================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// =================================================================================
package cardquery
import (
"context"
v1 "git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/api/cardquery/v1"
)
type ICardqueryV1 interface {
SimBase(ctx context.Context, req *v1.SimBaseReq) (res *v1.SimBaseRes, err error)
}
@@ -0,0 +1,15 @@
package v1
import (
"git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/model"
"github.com/gogf/gf/v2/frame/g"
)
// sim
type SimBaseReq struct {
g.Meta `path:"/sim/*any" tags:"CardQuery" method:"GET,POST" summary:"物联网卡基础请求"`
model.SimBaseReqJson
}
type SimBaseRes = model.SimBaseResJson
+12
View File
@@ -0,0 +1,12 @@
package channel
import (
"context"
v1 "git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/api/channel/v1"
)
type IChannelV1 interface {
Refresh(ctx context.Context, req *v1.RefreshReq) (res *v1.RefreshRes, err error)
Remove(ctx context.Context, req *v1.RemoveReq) (res *v1.RemoveRes, err error)
}
+23
View File
@@ -0,0 +1,23 @@
package v1
import "github.com/gogf/gf/v2/frame/g"
type RefreshReq struct {
g.Meta `path:"/internal/channel/refresh" tags:"Internal" method:"POST" summary:"Refresh channel cache"`
ChanKey string `json:"chanKey" in:"query" v:"required#chanKey is required"`
}
type RefreshRes struct {
ChanKey string `json:"chanKey"`
Refreshed bool `json:"refreshed"`
}
type RemoveReq struct {
g.Meta `path:"/internal/channel/remove" tags:"Internal" method:"POST" summary:"Remove channel cache"`
ChanKey string `json:"chanKey" in:"query" v:"required#chanKey is required"`
}
type RemoveRes struct {
ChanKey string `json:"chanKey"`
Removed bool `json:"removed"`
}
+38
View File
@@ -0,0 +1,38 @@
ARG BASE_REGISTRY=docker.io/library
FROM ${BASE_REGISTRY}/golang:1.25-alpine AS builder
LABEL stage=gobuilder
ENV GOPROXY=https://goproxy.cn,direct
# RUN apk update --no-cache && apk add --no-cache tzdata
# 安装GCC和相关依赖
# RUN apk add --no-cache gcc g++ make autoconf automake binutils-dev gmp-dev isl-dev patchelf
# RUN apk update --no-cache && apk add --no-cache tzdata git expect
WORKDIR /build
COPY go.mod go.sum ./
RUN go mod download
COPY . .
ARG BuildVersion
ARG BuildTime
ARG CommitID
RUN go build -o iotlcmupstreamproxy_svc -ldflags "-w -s -X main.BuildVersion=${BuildVersion} -X main.CommitID=${CommitID} -X main.BuildTime=${BuildTime}"
FROM ${BASE_REGISTRY}/alpine:3.23 AS alpine
RUN apk add --no-cache ca-certificates tzdata
# RUN apk update --no-cache && apk add --no-cache ca-certificates
ENV TZ=Asia/Shanghai
EXPOSE 11140
WORKDIR /app
COPY --from=builder /build/iotlcmupstreamproxy_svc /app/iotlcmupstreamproxy_svc
COPY --from=builder /build/build/config.example.yaml /app/config/config.yaml
CMD ["./iotlcmupstreamproxy_svc"]
+118
View File
@@ -0,0 +1,118 @@
# config.yaml 配置文件
# @Link https://github.com/xiujiecn/xiu-admin
# @Copyright Copyright (c) 2025 LiXiujie
# @Author Lxj <xxx@example.com>
# @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
# 系统配置
system:
name: "XiuAdmin"
version: "1.0"
description: "XiuAdmin Server"
gfmode: "dev"
upload:
local:
path: "resource/upload"
# 服务配置
server:
name: "yzupstreamproxy-http-svc"
address: ":8510"
# 接口文档
openapiPath: "/api.json" # OpenAPI接口文档地址
swaggerPath: "/swagger" # 内置SwaggerUI展示地址
# 静态服务配置
indexFiles: ["index.html","index.htm"] # 自动首页静态文件检索。默认为["index.html", "index.htm"]
indexFolder: false # 当访问静态文件目录时,是否展示目录下的文件列表。默认关闭,那么请求将返回403
# serverRoot: "resource/public" # 静态文件服务的目录根路径,配置时自动开启静态文件服务。默认关闭
# searchPaths: ["/resource/public"] # 提供静态文件服务时额外的文件搜索路径,当根路径找不到时则按照顺序在搜索目录查找。默认关闭
fileServerEnabled: true
initLoadMemoryDB: false # 是否初始化内存数据库,默认关闭
# 文件上传配置
maxHeaderBytes: "20KB"
clientMaxBodySize: "200MB"
registry:
type: "consul"
address: "127.0.0.1:8500"
# 日志配置
logger:
path: "/app/logs" # 日志文件路径。默认为空,表示关闭,仅输出到终端
file: "{Y-m-d-H}.log" # 日志文件格式。默认为"{Y-m-d}.log"
prefix: "" # 日志内容输出前缀。默认为空
level: "dev" # 日志输出级别
timeFormat: "2006-01-02T15:04:05" # 自定义日志输出的时间格式,使用Golang标准的时间格式配置
ctxKeys: [] # 自定义Context上下文变量名称,自动打印Context的变量到日志中。默认为空
header: true # 是否打印日志的头信息。默认true
stdout: true # 日志是否同时输出到终端。默认true
rotateSize: 0 # 按照日志文件大小对文件进行滚动切分。默认为0,表示关闭滚动切分特性
rotateExpire: 0 # 按照日志文件时间间隔对文件滚动切分。默认为0,表示关闭滚动切分特性
rotateBackupLimit: 0 # 按照切分的文件数量清理切分文件,当滚动切分特性开启时有效。默认为0,表示不备份,切分则删除
rotateBackupExpire: 0 # 按照切分的文件有效期清理切分文件,当滚动切分特性开启时有效。默认为0,表示不备份,切分则删除
rotateBackupCompress: 0 # 滚动切分文件的压缩比(0-9)。默认为0,表示不压缩
rotateCheckInterval: "1h" # 滚动切分的时间检测间隔,一般不需要设置。默认为1小时
stdoutColorDisabled: false # 关闭终端的颜色打印。默认开启
writerColorEnable: false # 日志文件是否带上颜色。默认false,表示不带颜色
database:
logger:
path: "/app/logs"
level: "all"
stdout: true
default: # 分组名称,可自定义,默认为default
link: "${DB_DSN}"
debug: true #开启调试模式
charset: "utf8mb4" # (可选)数据库编码(如: utf8mb4/utf8/gbk/gb2312),一般设置为utf8mb4。默认为utf8。
dryRun: false #ORM空跑(只读不写)
maxIdle: 30 #连接池最大闲置的连接数
maxOpen: 100 #连接池最大打开的连接数
maxLifetime: 120 #(单位秒)连接对象可重复使用的时间长度
memorydb:
# link: "sqlite::@file(jieiotmem.db)?journal_mode=WAL"
link: "${DB_DSN}"
# link: ""
debug: true
maxIdle: 1
maxOpen: 1
maxLifetime: 30
# 缓存配置
cache:
adapter: "redis" # 缓存适配器,支持:file|redis
fileDir: "resource/cache" # 缓存文件目录
# Redis 配置示例
redis:
# 单实例配置
default:
mode: "single" #redis模式,支持:single|cluster|sentinel
sentinelMasterName: "example-master" #哨兵模式主节点名称
address: 127.0.0.1:6382
db: 0
pass: "${SERVICE_PASSWORD}"
minIdle: 5 #最小空闲连接数
maxIdle: 30 #允许闲置的最大连接数
maxActive: 5000 #最大活跃连接数
idleTimeout: "60s" #连接最大空闲时间,使用时间字符串例如30s/2m/2d
ClientName: "SystemCache"
queue:
mode: "single" #redis模式,支持:single|cluster|sentinel
sentinelMasterName: "example-master" #哨兵模式主节点名称
address: 127.0.0.1:6382
db: 0
pass: "${SERVICE_PASSWORD}"
minIdle: 5 #最小空闲连接数
maxIdle: 30 #允许闲置的最大连接数
maxActive: 5000 #最大活跃连接数
idleTimeout: "60s" #连接最大空闲时间,使用时间字符串例如30s/2m/2d
ClientName: "QueueCache"
# 路由配置
router:
# 不需要验证登录的路由地址
exceptLogin:
- /v3/query/token
gateway:
address: "iotlcmupstreamchan-http-svc"
timeout: "10s"
+112
View File
@@ -0,0 +1,112 @@
# config.yaml 配置文件
# @Link https://github.com/xiujiecn/xiu-admin
# @Copyright Copyright (c) 2025 LiXiujie
# @Author Lxj <xxx@example.com>
# @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
# 系统配置
system:
name: "XiuAdmin"
version: "1.0"
description: "XiuAdmin Server"
gfmode: "dev"
upload:
local:
path: "resource/upload"
# 服务配置
server:
name: "yzupstreamproxy-http-svc"
address: ":8510"
# 接口文档
openapiPath: "/api.json" # OpenAPI接口文档地址
swaggerPath: "/swagger" # 内置SwaggerUI展示地址
# 静态服务配置
indexFiles: ["index.html","index.htm"] # 自动首页静态文件检索。默认为["index.html", "index.htm"]
indexFolder: false # 当访问静态文件目录时,是否展示目录下的文件列表。默认关闭,那么请求将返回403
# serverRoot: "resource/public" # 静态文件服务的目录根路径,配置时自动开启静态文件服务。默认关闭
# searchPaths: ["/resource/public"] # 提供静态文件服务时额外的文件搜索路径,当根路径找不到时则按照顺序在搜索目录查找。默认关闭
fileServerEnabled: true
initLoadMemoryDB: false # 是否初始化内存数据库,默认关闭
# 文件上传配置
maxHeaderBytes: "20KB"
clientMaxBodySize: "200MB"
registry:
type: "consul"
address: "127.0.0.1:8500"
# address: "127.0.0.1:8500"
# 日志配置
logger:
path: "/app/logs" # 日志文件路径。默认为空,表示关闭,仅输出到终端
file: "{Y-m-d-H}.log" # 日志文件格式。默认为"{Y-m-d}.log"
prefix: "" # 日志内容输出前缀。默认为空
level: "dev" # 日志输出级别
timeFormat: "2006-01-02T15:04:05" # 自定义日志输出的时间格式,使用Golang标准的时间格式配置
ctxKeys: [] # 自定义Context上下文变量名称,自动打印Context的变量到日志中。默认为空
header: true # 是否打印日志的头信息。默认true
stdout: true # 日志是否同时输出到终端。默认true
rotateSize: 0 # 按照日志文件大小对文件进行滚动切分。默认为0,表示关闭滚动切分特性
rotateExpire: 0 # 按照日志文件时间间隔对文件滚动切分。默认为0,表示关闭滚动切分特性
rotateBackupLimit: 0 # 按照切分的文件数量清理切分文件,当滚动切分特性开启时有效。默认为0,表示不备份,切分则删除
rotateBackupExpire: 0 # 按照切分的文件有效期清理切分文件,当滚动切分特性开启时有效。默认为0,表示不备份,切分则删除
rotateBackupCompress: 0 # 滚动切分文件的压缩比(0-9)。默认为0,表示不压缩
rotateCheckInterval: "1h" # 滚动切分的时间检测间隔,一般不需要设置。默认为1小时
stdoutColorDisabled: false # 关闭终端的颜色打印。默认开启
writerColorEnable: false # 日志文件是否带上颜色。默认false,表示不带颜色
database:
logger:
path: "/app/logs/sql"
level: "all"
stdout: true
default: # 分组名称,可自定义,默认为default
link: "${DB_DSN}"
debug: true #开启调试模式
charset: "utf8mb4" # (可选)数据库编码(如: utf8mb4/utf8/gbk/gb2312),一般设置为utf8mb4。默认为utf8。
dryRun: false #ORM空跑(只读不写)
maxIdle: 30 #连接池最大闲置的连接数
maxOpen: 100 #连接池最大打开的连接数
maxLifetime: 120 #(单位秒)连接对象可重复使用的时间长度
memorydb:
# link: "sqlite::@file(jieiotmem.db)?journal_mode=WAL"
link: "${DB_DSN}"
# link: ""
debug: true
maxIdle: 1
maxOpen: 1
maxLifetime: 30
# 缓存配置
cache:
adapter: "redis" # 缓存适配器,支持:file|redis
fileDir: "resource/cache" # 缓存文件目录
# Redis 配置示例
redis:
# 单实例配置
default:
mode: "single" #redis模式,支持:single|cluster|sentinel
sentinelMasterName: "example-master" #哨兵模式主节点名称
address: 127.0.0.1:6379
db: 0
pass: "${SERVICE_PASSWORD}"
minIdle: 5 #最小空闲连接数
maxIdle: 30 #允许闲置的最大连接数
maxActive: 5000 #最大活跃连接数
idleTimeout: "60s" #连接最大空闲时间,使用时间字符串例如30s/2m/2d
ClientName: "SystemCache"
# 路由配置
# router:
# 不需要验证登录的路由地址
# exceptLogin:
# - /v3/query/token
# 上游通道服务
gateway:
# 上游通道服务名称
address: "iotlcmupstreamchan-http-svc"
# 调用上游通道服务超时时间,默认10(秒)
timeout: "10s"
+73
View File
@@ -0,0 +1,73 @@
module git.5iot.com/iotlcm-public/iotlcm-upstream-proxy
go 1.25.5
require (
github.com/gogf/gf/contrib/drivers/mysql/v2 v2.10.0
github.com/gogf/gf/contrib/drivers/sqlite/v2 v2.10.0
github.com/gogf/gf/contrib/nosql/redis/v2 v2.10.0
github.com/gogf/gf/contrib/rpc/grpcx/v2 v2.10.0
github.com/gogf/gf/v2 v2.10.3
golang.org/x/sync v0.20.0
google.golang.org/grpc v1.79.3
)
require (
github.com/BurntSushi/toml v1.5.0 // indirect
github.com/armon/go-metrics v0.4.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/clbanning/mxj/v2 v2.7.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/emirpasic/gods/v2 v2.0.0-alpha // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/glebarez/go-sqlite v1.21.2 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-sql-driver/mysql v1.7.1 // indirect
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/gogf/gf/contrib/registry/consul/v2 v2.10.3
github.com/gogf/gf/contrib/registry/file/v2 v2.10.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/grokify/html-strip-tags-go v0.1.0 // indirect
github.com/hashicorp/consul/api v1.33.2 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-hclog v1.5.0 // indirect
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
github.com/hashicorp/golang-lru v0.5.4 // indirect
github.com/hashicorp/serf v0.10.1 // indirect
github.com/magiconair/properties v1.8.10 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/olekukonko/errors v1.1.0 // indirect
github.com/olekukonko/ll v0.0.9 // indirect
github.com/olekukonko/tablewriter v1.1.0 // indirect
github.com/redis/go-redis/v9 v9.18.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/tjfoc/gmsm v1.4.1 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/otel v1.39.0 // indirect
go.opentelemetry.io/otel/metric v1.39.0 // indirect
go.opentelemetry.io/otel/sdk v1.39.0 // indirect
go.opentelemetry.io/otel/trace v1.39.0 // indirect
go.uber.org/atomic v1.11.0 // indirect
golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90 // indirect
golang.org/x/net v0.48.0 // indirect
golang.org/x/sys v0.39.0 // indirect
golang.org/x/text v0.32.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
google.golang.org/protobuf v1.36.11
gopkg.in/yaml.v3 v3.0.1 // indirect
modernc.org/libc v1.22.5 // indirect
modernc.org/mathutil v1.5.0 // indirect
modernc.org/memory v1.5.0 // indirect
modernc.org/sqlite v1.23.1 // indirect
)
+402
View File
@@ -0,0 +1,402 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA=
github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME=
github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/emirpasic/gods/v2 v2.0.0-alpha h1:dwFlh8pBg1VMOXWGipNMRt8v96dKAIvBehtCt6OtunU=
github.com/emirpasic/gods/v2 v2.0.0-alpha/go.mod h1:W0y4M2dtBB9U5z3YlghmpuUhiaZT2h6yoeE+C1sCp6A=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo=
github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/gogf/gf/contrib/drivers/mysql/v2 v2.10.0 h1:UvqxwinkelKxwdwnKUfdy51/ls4RL7MCeJqAZOVAy0I=
github.com/gogf/gf/contrib/drivers/mysql/v2 v2.10.0/go.mod h1:6v7oGBF9wv59WERJIOJxXmLhkUcxwON3tPYW3AZ7wbY=
github.com/gogf/gf/contrib/drivers/sqlite/v2 v2.10.0 h1:OyAH7Ls2c9Un7CJiAq7G6eY1jWIICRkN8C5SyM94rnY=
github.com/gogf/gf/contrib/drivers/sqlite/v2 v2.10.0/go.mod h1:fwhAMG0qZpeHbbP2JE78rJRfV7eBbu9jXkxTMM1lwyo=
github.com/gogf/gf/contrib/nosql/redis/v2 v2.10.0 h1:N/F9CuDdUZLoM1nVRqrDE/33pDZuhVxpNY4wYdeIaBs=
github.com/gogf/gf/contrib/nosql/redis/v2 v2.10.0/go.mod h1:x6uoJGfZOtirIRQls8xUlYzC6f7T/eULPUa9er368X0=
github.com/gogf/gf/contrib/registry/consul/v2 v2.10.3 h1:h2tRpKTlEkBVnpmWdqrF5l6LmzjheDXAwSpR/zruxPM=
github.com/gogf/gf/contrib/registry/consul/v2 v2.10.3/go.mod h1:9Dk7HM3fXJxQp34CXWZEeHceJZwrMN1G4xLRVAgcdjs=
github.com/gogf/gf/contrib/registry/file/v2 v2.10.0 h1:yy93PGhz2CE3PpF2JSGa2MXJWd4JNHYC5l5NEFCFRV4=
github.com/gogf/gf/contrib/registry/file/v2 v2.10.0/go.mod h1:ZzGWiTbQ9nAmDynrmzaRJlqYHD/F3MeMwCvHrkv/LP4=
github.com/gogf/gf/contrib/rpc/grpcx/v2 v2.10.0 h1:MWHgX/h/62+Oo1Jbeq8DrXbEWSOZxskpBBm3cFAV4/Q=
github.com/gogf/gf/contrib/rpc/grpcx/v2 v2.10.0/go.mod h1:zGZdjS08IqiSGhKfy69/qgcYKUYUL/VsOx5fuT++TVE=
github.com/gogf/gf/v2 v2.10.0 h1:rzDROlyqGMe/eM6dCalSR8dZOuMIdLhmxKSH1DGhbFs=
github.com/gogf/gf/v2 v2.10.0/go.mod h1:Svl1N+E8G/QshU2DUbh/3J/AJauqCgUnxHurXWR4Qx0=
github.com/gogf/gf/v2 v2.10.3 h1:tZvEk+58x9uyuOAMgT5VV8yAPHGPBwJkPYRuXj2cfBI=
github.com/gogf/gf/v2 v2.10.3/go.mod h1:Svl1N+E8G/QshU2DUbh/3J/AJauqCgUnxHurXWR4Qx0=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grokify/html-strip-tags-go v0.1.0 h1:03UrQLjAny8xci+R+qjCce/MYnpNXCtgzltlQbOBae4=
github.com/grokify/html-strip-tags-go v0.1.0/go.mod h1:ZdzgfHEzAfz9X6Xe5eBLVblWIxXfYSQ40S/VKrAOGpc=
github.com/hashicorp/consul/api v1.33.2 h1:Q6mE0WZsUTJerlnl9TuXzqrtZ0cKdOCsxcZhj5mKbMs=
github.com/hashicorp/consul/api v1.33.2/go.mod h1:K3yoL/vnIBcQV/25NeMZVokRvPPERiqp2Udtr4xAfhs=
github.com/hashicorp/consul/sdk v0.17.1 h1:LumAh8larSXmXw2wvw/lK5ZALkJ2wK8VRwWMLVV5M5c=
github.com/hashicorp/consul/sdk v0.17.1/go.mod h1:EngiixMhmw9T7wApycq6rDRFXXVUwjjf7HuLiGMH/Sw=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c=
github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc=
github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI=
github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc=
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc=
github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI=
github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc=
github.com/hashicorp/memberlist v0.5.0 h1:EtYPN8DpAURiapus508I4n9CzHs2W+8NZGbmmR/prTM=
github.com/hashicorp/memberlist v0.5.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4mHgHUZ8lrOI0=
github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY=
github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/klauspost/cpuid/v2 v2.2.3 h1:sxCkb+qR91z4vsqw4vGGZlDgPz3G7gjaLyK3V8y70BU=
github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
github.com/miekg/dns v1.1.41 h1:WMszZWJG0XmzbK9FEmzH2TVcqYzFesusSIB41b8KHxY=
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/olekukonko/errors v1.1.0 h1:RNuGIh15QdDenh+hNvKrJkmxxjV4hcS50Db478Ou5sM=
github.com/olekukonko/errors v1.1.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y=
github.com/olekukonko/ll v0.0.9 h1:Y+1YqDfVkqMWuEQMclsF9HUR5+a82+dxJuL1HHSRpxI=
github.com/olekukonko/ll v0.0.9/go.mod h1:En+sEW0JNETl26+K8eZ6/W4UQ7CYSrrgg/EdIYT2H8g=
github.com/olekukonko/tablewriter v1.1.0 h1:N0LHrshF4T39KvI96fn6GT8HEjXRXYNDrDjKFDB7RIY=
github.com/olekukonko/tablewriter v1.1.0/go.mod h1:5c+EBPeSqvXnLLgkm9isDdzR3wjfBkHR9Nhfp3NWrzo=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
github.com/redis/go-redis/v9 v9.18.0 h1:pMkxYPkEbMPwRdenAzUNyFNrDgHx9U+DrBabWNfSRQs=
github.com/redis/go-redis/v9 v9.18.0/go.mod h1:k3ufPphLU5YXwNTUcCRXGxUoF1fqxnhFQmscfkCoDA0=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=
github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90 h1:jiDhWWeC7jfWqR9c/uplMOqJ0sbNlNWv0UkzE0vX1MA=
golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90/go.mod h1:xE1HEv6b+1SCZ5/uscMRjUBKtIxworgEcEi+/n9NQDQ=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE=
modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY=
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds=
modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
modernc.org/sqlite v1.23.1 h1:nrSBg4aRQQwq59JpvGEQ15tNxoO5pX/kUjcRNwSAGQM=
modernc.org/sqlite v1.23.1/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk=
+19
View File
@@ -0,0 +1,19 @@
# CLI tool, only in development environment.
# https://goframe.org/docs/cli
gfcli:
gen:
dao:
link: "${DB_DSN}"
tables: "t_sim_card_info, t_sim_channel_templatem, t_sim_channels"
descriptionTag: true # 数据表字段注释
noModelComment: true
jsonCase: "CamelLower" # 生成json字段时,字段名使用驼峰命名
gJsonSupport: true
group: "default" # 分组 使用代码生成功能时必须填
tablesEx: "sys_addons_install" # 指定当前数据库中需要排除代码生成的数据表。
docker:
build: "-a amd64 -s linux -p temp -ew"
tagPrefixes:
- my.image.pub/my-app
+20
View File
@@ -0,0 +1,20 @@
# Install/Update to the latest CLI tool.
.PHONY: cli
cli:
@set -e; \
wget -O gf \
https://github.com/gogf/gf/releases/latest/download/gf_$(shell go env GOOS)_$(shell go env GOARCH) && \
chmod +x gf && \
./gf install -y && \
rm ./gf
# Check and install CLI tool.
.PHONY: cli.install
cli.install:
@set -e; \
gf -v > /dev/null 2>&1 || if [[ "$?" -ne "0" ]]; then \
echo "GoFame CLI is not installed, start proceeding auto installation..."; \
make cli; \
fi;
+94
View File
@@ -0,0 +1,94 @@
.DEFAULT_GOAL := build
# Update GoFrame and its CLI to latest stable version.
.PHONY: up
up: cli.install
@gf up -a
# Build binary using configuration from hack/config.example.yaml.
.PHONY: build
build: cli.install
@gf build -ew
# Parse api and generate controller/sdk.
.PHONY: ctrl
ctrl: cli.install
@gf gen ctrl -m
# Generate Go files for DAO/DO/Entity.
.PHONY: dao
dao: cli.install
@gf gen dao
# Parse current project go files and generate enums go file.
.PHONY: enums
enums: cli.install
@gf gen enums
# Generate Go files for Service.
.PHONY: service
service: cli.install
@gf gen service
# Build docker image.
.PHONY: image
image: cli.install
$(eval _TAG = $(shell git rev-parse --short HEAD))
ifneq (, $(shell git status --porcelain 2>/dev/null))
$(eval _TAG = $(_TAG).dirty)
endif
$(eval _TAG = $(if ${TAG}, ${TAG}, $(_TAG)))
$(eval _PUSH = $(if ${PUSH}, ${PUSH}, ))
@gf docker ${_PUSH} -tn $(DOCKER_NAME):${_TAG};
# Build docker image and automatically push to docker repo.
.PHONY: image.push
image.push: cli.install
@make image PUSH=-p;
# Deploy image and yaml to current kubectl environment.
.PHONY: deploy
deploy: cli.install
$(eval _TAG = $(if ${TAG}, ${TAG}, develop))
@set -e; \
mkdir -p $(ROOT_DIR)/temp/kustomize;\
cd $(ROOT_DIR)/manifest/deploy/kustomize/overlays/${_ENV};\
kustomize build > $(ROOT_DIR)/temp/kustomize.yaml;\
kubectl apply -f $(ROOT_DIR)/temp/kustomize.yaml; \
if [ $(DEPLOY_NAME) != "" ]; then \
kubectl patch -n $(NAMESPACE) deployment/$(DEPLOY_NAME) -p "{\"spec\":{\"template\":{\"metadata\":{\"labels\":{\"date\":\"$(shell date +%s)\"}}}}}"; \
fi;
# Parsing protobuf files and generating go files.
.PHONY: pb
pb: cli.install
@gf gen pb
# Generate protobuf files for database tables.
.PHONY: pbentity
pbentity: cli.install
@gf gen pbentity
export AppName=jieiot_linux
export BuildVersion=$(shell git describe --abbrev=0 --tags)
export BuildTime=$(shell date +%FT%T%z)
export CommitID=$(shell git rev-parse HEAD)
export NOWTIME=$(shell date "+%Y%m%d%H%M%S")
export HasImages=$(shell docker images | grep docker_iotlcmupstreamproxy:v0 | grep -v docker_iotlcmupstreamproxy:v0.)
.PHONY: docker_iotlcmupstreamproxy
docker_iotlcmupstreamproxy:
if [ -n "${HasImages}" ]; then \
docker tag docker_iotlcmupstreamproxy:v0 docker_iotlcmupstreamproxy:v0.${NOWTIME}.bak; \
docker rmi docker_iotlcmupstreamproxy:v0; \
fi
docker build --rm -f build/Dockerfile --progress=plain --platform linux/amd64 -t docker_iotlcmupstreamproxy:v0 --build-arg BuildVersion=${BuildVersion} --build-arg BuildTime=${BuildTime} --build-arg CommitID=${CommitID} .
# docker save -o docker_iotlcmupstreamproxy.tar docker_iotlcmupstreamproxy:v0
dockerbuild: docker_iotlcmupstreamproxy
echo "docker build success"
+98
View File
@@ -0,0 +1,98 @@
package cmd
import (
"context"
"fmt"
"git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/controller/infopb"
"git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/controller/msgcallpb"
"git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/service"
"git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/pub/xjpubconsts"
"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"
"github.com/gogf/gf/v2/net/gsvc"
"github.com/gogf/gf/v2/os/gcmd"
"github.com/gogf/gf/v2/util/gconv"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
)
const (
ServiceNamePrefix = "iotlcmupstreamproxy"
)
var (
gGrpcPort = 8700
gAccessIP = "0.0.0.0"
)
var (
Main = gcmd.Command{
Name: "main",
Usage: "main",
Brief: "start http server",
Func: func(ctx context.Context, parser *gcmd.Parser) (err error) {
s := g.Server()
gGrpcPort = gconv.Int(gcmd.GetOptWithEnv("grpcport"))
gAccessIP = gconv.String(gcmd.GetOptWithEnv("accessip"))
if len(gAccessIP) == 0 {
gAccessIP = "0.0.0.0"
}
if gGrpcPort == 0 {
gGrpcPort = 8110
}
nodeName := fmt.Sprintf("%s", ServiceNamePrefix)
xjpubservice.Info().SetInfoName(xjpubconsts.ServiceTypeAccess, fmt.Sprintf("%s-http-svc", ServiceNamePrefix), "上游运营商渠道服务", "上游运营商渠道服务")
xjpubservice.Info().AddInfoData("grpcport", gGrpcPort)
xjpubservice.Info().AddInfoData("accessip", gAccessIP)
xjpubservice.Info().AddInfoData("nodeName", nodeName)
xjpubservice.InfoRpc().SetInfoName(xjpubconsts.ServiceTypeAccess, fmt.Sprintf("%s-grpc-svc", ServiceNamePrefix), "上游运营商渠道服务", "上游运营商渠道服务")
xjpubservice.InfoRpc().AddInfoData("grpcport", gGrpcPort)
xjpubservice.InfoRpc().AddInfoData("accessip", gAccessIP)
xjpubservice.InfoRpc().AddInfoData("nodeName", nodeName)
bindHTTPRoutes(s)
err = service.ChannelConfig().Start(ctx)
if err != nil {
return err
}
err = service.UpstreamAPI().Start(ctx)
if err != nil {
return err
}
go func() {
c := grpcx.Server.NewConfig()
c.Endpoints = []string{fmt.Sprintf("%s:%d", gAccessIP, gGrpcPort)}
c.Options = append(c.Options, []grpc.ServerOption{
grpcx.Server.ChainUnary(
grpcx.Server.UnaryValidate,
),
}...)
sGRPC := grpcx.Server.New(c)
sGRPC.Service(&gsvc.LocalService{
Name: fmt.Sprintf("%s-grpc-svc", ServiceNamePrefix),
Endpoints: gsvc.Endpoints{gsvc.NewEndpoint(fmt.Sprintf("%s:%d", gAccessIP, gGrpcPort))},
Metadata: gsvc.Metadata{
gsvc.MDProtocol: "grpc",
gsvc.MDInsecure: true,
},
})
infopb.Register(sGRPC)
msgcallpb.Register(sGRPC)
reflection.Register(sGRPC.Server)
g.Log().Infof(ctx, "GRPC Server启动, listenPort:%d", gGrpcPort)
sGRPC.Run()
g.Log().Infof(ctx, "GRPC Server堵塞运行结束")
}()
s.Run()
service.UpstreamAPI().Stop(ctx)
service.ChannelConfig().Stop(ctx)
return nil
},
}
)
+19
View File
@@ -0,0 +1,19 @@
package cmd
import (
"git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/controller/cardquery"
"git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/controller/channel"
"github.com/gogf/gf/v2/net/ghttp"
)
func bindHTTPRoutes(server *ghttp.Server) {
server.Group("/", func(group *ghttp.RouterGroup) {
group.Middleware(ghttp.MiddlewareHandlerResponse)
group.Bind(
cardquery.NewV1(),
channel.NewV1(),
)
group.POST("/internal/app-diagnosis/sim/*any", cardquery.AppDiagnosisQuery)
})
}
+160
View File
@@ -0,0 +1,160 @@
package cmd
import (
"context"
"crypto/sha256"
"encoding/json"
"fmt"
"net/url"
"os"
"strings"
"testing"
"time"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/os/gcfg"
v1 "git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/api/cardquery/v1"
_ "git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/logic/simKeyFill"
"git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/logic/upstreamapi"
"git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/logic/upstreamimpl"
"git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/model"
"git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/service"
)
func TestAPNHTTPHarness(t *testing.T) {
if os.Getenv("APN_HTTP_CHAIN_HELPER") != "1" {
t.Skip("未启用本机HTTP模拟链")
}
gateway, err := url.Parse(os.Getenv("APN_HTTP_CHAIN_CHAN"))
if err != nil || gateway.Scheme != "http" || gateway.Hostname() != "127.0.0.1" || gateway.Port() == "" {
t.Fatal("只允许本机模拟chan")
}
adapter, err := gcfg.NewAdapterContent(fmt.Sprintf(`{"gateway":{"address":%q,"timeout":"2s"}}`, gateway.Host))
if err != nil {
t.Fatal(err)
}
g.Cfg().SetAdapter(adapter)
api := upstreamapi.NewUpstreamAPI()
api.RegisterChan("fake-channel", "1", &model.SimCardChannelCache{Instance: upstreamimpl.NewSimYD()})
service.RegisterUpstreamAPI(api)
server := ghttp.GetServer(t.Name())
server.SetAddr("127.0.0.1:0")
bindHTTPRoutes(server)
stop := make(chan struct{})
server.BindHandler("POST:/__test/stop", func(r *ghttp.Request) { close(stop) })
if err := server.Start(); err != nil {
t.Fatal(err)
}
defer server.Shutdown()
fmt.Printf("APN_CHAIN_READY=http://127.0.0.1:%d\n", server.GetListenedPort())
select {
case <-stop:
case <-time.After(3 * time.Minute):
t.Error("HTTP模拟链未正常结束")
}
}
type apnBindingTestFill struct {
service.ISimKeyFill
captured chan model.SimBaseReqJson
}
func (s *apnBindingTestFill) SimKeyFill(_ context.Context, input *model.SimBaseReqJson) (model.SimBaseResJson, error) {
s.captured <- *input
return model.SimBaseResJson{}, nil
}
func TestAPNProductionHTTPBindingPreservesRawPayload(t *testing.T) {
old := service.SimKeyFill()
capture := &apnBindingTestFill{ISimKeyFill: old, captured: make(chan model.SimBaseReqJson, 1)}
service.RegisterSimKeyFill(capture)
t.Cleanup(func() { service.RegisterSimKeyFill(old) })
server := ghttp.GetServer(t.Name())
server.SetAddr("127.0.0.1:0")
bindHTTPRoutes(server)
server.BindHandler("POST:/__test/legacy-binding", func(r *ghttp.Request) {
var req v1.SimBaseReq
if err := r.Parse(&req); err != nil {
t.Error(err)
r.Response.WriteStatus(400)
return
}
capture.captured <- req.SimBaseReqJson
})
if err := server.Start(); err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _ = server.Shutdown() })
for _, data := range []string{
`{"operationId":"frozen-operation","submitOnly":true,"operType":"1","apnName":"CMIOT"}`,
`{"operationId":"frozen-operation","queryJobId":"saved-job","operType":"1","apnName":"CMIOT"}`,
`{"operationId":"frozen-operation","recoverOnly":true,"operType":"1","apnName":"CMIOT"}`,
} {
for _, path := range []string{"/sim/change/sim-apn", "/sim/query/sim-apn-capability", "/sim/query/sim-session"} {
input := model.SimBaseReqJson{SimKeys: []*model.SimKey{{ChanKey: "fake", ICCID: "fake-card", MSISDN: "frozen-number"}}, ReqData: json.RawMessage(data)}
response, err := g.Client().ContentJson().Post(context.Background(), fmt.Sprintf("http://127.0.0.1:%d%s", server.GetListenedPort(), path), input)
if err != nil {
t.Fatal(err)
}
body := response.ReadAll()
_ = response.Close()
select {
case got := <-capture.captured:
var wantData, gotData map[string]any
_ = json.Unmarshal(input.ReqData, &wantData)
_ = json.Unmarshal(got.ReqData, &gotData)
wantJSON, _ := json.Marshal(wantData)
gotJSON, _ := json.Marshal(gotData)
if path == "/sim/query/sim-session" {
baselineResponse, err := g.Client().ContentJson().Post(context.Background(), fmt.Sprintf("http://127.0.0.1:%d/__test/legacy-binding", server.GetListenedPort()), input)
if err != nil {
t.Fatal(err)
}
_ = baselineResponse.Close()
baseline := <-capture.captured
if string(got.ReqData) != string(baseline.ReqData) {
t.Fatalf("非APN旧绑定行为改变:got=%q baseline=%q", got.ReqData, baseline.ReqData)
}
} else if sha256.Sum256(wantJSON) != sha256.Sum256(gotJSON) {
t.Fatalf("APN冻结请求摘要改变:path=%s want=%s got=%s", path, wantJSON, gotJSON)
}
if len(got.SimKeys) != 1 || *got.SimKeys[0] != *input.SimKeys[0] {
t.Fatalf("代理生产绑定丢字段:%+v", got)
}
default:
t.Fatalf("未经过生产代理controller:%s", body)
}
}
}
}
func TestBindHTTPRoutesRegistersChannelControlRoutes(t *testing.T) {
server := ghttp.GetServer("proxy-channel-refresh-route-test")
server.SetAddr("127.0.0.1:0")
bindHTTPRoutes(server)
if err := server.Start(); err != nil {
t.Fatalf("start HTTP server: %v", err)
}
t.Cleanup(func() {
if err := server.Shutdown(); err != nil {
t.Errorf("shutdown HTTP server: %v", err)
}
})
want := map[string]bool{
"/internal/channel/refresh": false,
"/internal/channel/remove": false,
"/internal/app-diagnosis/sim/*any": false,
}
for _, route := range server.GetRoutes() {
if _, ok := want[route.Route]; ok && strings.EqualFold(route.Method, "POST") {
want[route.Route] = true
}
}
for route, found := range want {
if !found {
t.Fatalf("POST %s route was not registered: %+v", route, server.GetRoutes())
}
}
}
+6
View File
@@ -0,0 +1,6 @@
package consts
const (
ApiStatusSuccess = 0
ApiStatusError = 9999
)
@@ -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,65 @@
package cardquery
import (
"context"
"encoding/json"
"strings"
"github.com/gogf/gf/v2/frame/g"
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) {
request := g.RequestFromCtx(ctx)
if request != nil && (request.URL.Path == "/sim/change/sim-apn" || request.URL.Path == "/sim/query/sim-apn-capability") && strings.HasPrefix(strings.ToLower(request.Header.Get("Content-Type")), "application/json") {
// GoFrame通用字段转换不能保留json.RawMessage,APN协议直接按原始JSON绑定。
if err := json.Unmarshal(request.GetBody(), &req.SimBaseReqJson); err != nil {
return nil, err
}
}
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
}
@@ -0,0 +1,187 @@
// ==========================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// ==========================================================================
package internal
import (
"context"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
)
// TSimCardInfoDao is the data access object for the table t_sim_card_info.
type TSimCardInfoDao struct {
table string // table is the underlying table name of the DAO.
group string // group is the database configuration group name of the current DAO.
columns TSimCardInfoColumns // columns contains all the column names of Table for convenient usage.
handlers []gdb.ModelHandler // handlers for customized model modification.
}
// TSimCardInfoColumns defines and stores column names for the table t_sim_card_info.
type TSimCardInfoColumns struct {
Id string // 主键ID
Iccid string // ICCID
Msisdn string // MSISDN/卡号
Imsi string // IMSI
Imei string // IMEI
RegionCode string // 归属地
DeptId string // 渠道客户ID
CurrentPackageCode string // 当前套餐编号
PackageStatus string // 套餐状态:0 无套餐,1待生效,2生效中,3已过期
AccessType string // 接入方式:ipv4、ipv6、ipv4_ipv6
BossCardStatus string // 运营商卡状态 枚举
CardType string // 卡类型 枚举
BossOpenAt string // 运营商开户日期
BossActivateAt string // 运营商激活日期
IsBossActivateEnabled string // 是否按运营商激活减去赠送时长方:1是,0否
InboundBatchNo string // 入库批次
OutboundBatchNo string // 出库批次
AllocationBatchNo string // 划拨批次
IsFlowPool string // 是否为池卡:1是,0否
FlowPoolCode string // 流量池编号
FlowPkgBeginAt string // 当前套餐生效时间
FlowPkgPresentAt string // 套餐赠送结束时间
FlowPkgRealPresentAt string // 实际赠送结束时间
FlowPkgEndAt string // 当前套餐预计到期时间
DataServiceStatus string // 数据服务状态:1启用、0禁用
PackageTotalFlowMb string // 套餐总流量MB
UsedFlowMb string // 已用流量MB
TrafficCutoffStatus string // 达量断网配置状态:0未配置,1已开启,2已关闭
TrafficCutoffThreshold string // 达量断网阈值
SmsServiceStatus string // 短信服务状态:1启用、0禁用
PackageTotalSms string // 套餐短信总条数
UsedSms string // 已用短信条数
VoiceServiceStatus string // 语音服务状态:1启用、0禁用
PackageTotalVoice string // 套餐语音总分钟数
UsedVoice string // 已用语音分钟数
RoamServiceStatus string // 国漫服务状态:1启用、0禁用
PackageTotalRoamFlowMb string // 套餐国际流量MB
UsedRoamFlowMb string // 已用国际流量MB
StatusSyncTime string // 状态最后一次同步时间
FlowSyncTime string // 流量最后一次同步时间
SmsSyncTime string // 短信最后一次同步时间
VoiceSyncTime string // 语音最后一次同步时间
RoamSyncTime string // 国漫最后一次同步时间
ExtraFeeEnabled string // 是否收取附加费:1是,0否
ExtraFeeMultiple string // 附加费用倍数
ExtraFee string // 附加金额
ChanKey string // 通道编号
NetModel string // 网络类型:4G、5G、NB-IoT
Remark string // 备注
TenantId string // 租户ID
CreatedDept string // 创建部门
CreatedBy string // 创建者
CreatedAt string // 创建时间
UpdatedBy string // 更新者
UpdatedAt string // 更新时间
}
// tSimCardInfoColumns holds the columns for the table t_sim_card_info.
var tSimCardInfoColumns = TSimCardInfoColumns{
Id: "id",
Iccid: "iccid",
Msisdn: "msisdn",
Imsi: "imsi",
Imei: "imei",
RegionCode: "region_code",
DeptId: "dept_id",
CurrentPackageCode: "current_package_code",
PackageStatus: "package_status",
AccessType: "access_type",
BossCardStatus: "boss_card_status",
CardType: "card_type",
BossOpenAt: "boss_open_at",
BossActivateAt: "boss_activate_at",
IsBossActivateEnabled: "is_boss_activate_enabled",
InboundBatchNo: "inbound_batch_no",
OutboundBatchNo: "outbound_batch_no",
AllocationBatchNo: "allocation_batch_no",
IsFlowPool: "is_flow_pool",
FlowPoolCode: "flow_pool_code",
FlowPkgBeginAt: "flow_pkg_begin_at",
FlowPkgPresentAt: "flow_pkg_present_at",
FlowPkgRealPresentAt: "flow_pkg_real_present_at",
FlowPkgEndAt: "flow_pkg_end_at",
DataServiceStatus: "data_service_status",
PackageTotalFlowMb: "package_total_flow_mb",
UsedFlowMb: "used_flow_mb",
TrafficCutoffStatus: "traffic_cutoff_status",
TrafficCutoffThreshold: "traffic_cutoff_threshold",
SmsServiceStatus: "sms_service_status",
PackageTotalSms: "package_total_sms",
UsedSms: "used_sms",
VoiceServiceStatus: "voice_service_status",
PackageTotalVoice: "package_total_voice",
UsedVoice: "used_voice",
RoamServiceStatus: "roam_service_status",
PackageTotalRoamFlowMb: "package_total_roam_flow_mb",
UsedRoamFlowMb: "used_roam_flow_mb",
StatusSyncTime: "status_sync_time",
FlowSyncTime: "flow_sync_time",
SmsSyncTime: "sms_sync_time",
VoiceSyncTime: "voice_sync_time",
RoamSyncTime: "roam_sync_time",
ExtraFeeEnabled: "extra_fee_enabled",
ExtraFeeMultiple: "extra_fee_multiple",
ExtraFee: "extra_fee",
ChanKey: "chan_key",
NetModel: "net_model",
Remark: "remark",
TenantId: "tenant_id",
CreatedDept: "created_dept",
CreatedBy: "created_by",
CreatedAt: "created_at",
UpdatedBy: "updated_by",
UpdatedAt: "updated_at",
}
// NewTSimCardInfoDao creates and returns a new DAO object for table data access.
func NewTSimCardInfoDao(handlers ...gdb.ModelHandler) *TSimCardInfoDao {
return &TSimCardInfoDao{
group: "default",
table: "t_sim_card_info",
columns: tSimCardInfoColumns,
handlers: handlers,
}
}
// DB retrieves and returns the underlying raw database management object of the current DAO.
func (dao *TSimCardInfoDao) DB() gdb.DB {
return g.DB(dao.group)
}
// Table returns the table name of the current DAO.
func (dao *TSimCardInfoDao) Table() string {
return dao.table
}
// Columns returns all column names of the current DAO.
func (dao *TSimCardInfoDao) Columns() TSimCardInfoColumns {
return dao.columns
}
// Group returns the database configuration group name of the current DAO.
func (dao *TSimCardInfoDao) Group() string {
return dao.group
}
// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation.
func (dao *TSimCardInfoDao) Ctx(ctx context.Context) *gdb.Model {
model := dao.DB().Model(dao.table)
for _, handler := range dao.handlers {
model = handler(model)
}
return model.Safe().Ctx(ctx)
}
// Transaction wraps the transaction logic using function f.
// It rolls back the transaction and returns the error if function f returns a non-nil error.
// It commits the transaction and returns nil if function f returns nil.
//
// Note: Do not commit or roll back the transaction in function f,
// as it is automatically handled by this function.
func (dao *TSimCardInfoDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
return dao.Ctx(ctx).Transaction(ctx, f)
}
@@ -0,0 +1,101 @@
// ==========================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// ==========================================================================
package internal
import (
"context"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
)
// TSimChannelTemplateDao is the data access object for the table t_sim_channel_template.
type TSimChannelTemplateDao struct {
table string // table is the underlying table name of the DAO.
group string // group is the database configuration group name of the current DAO.
columns TSimChannelTemplateColumns // columns contains all the column names of Table for convenient usage.
handlers []gdb.ModelHandler // handlers for customized model modification.
}
// TSimChannelTemplateColumns defines and stores column names for the table t_sim_channel_template.
type TSimChannelTemplateColumns struct {
Id string // ID
TempKey string // 模板标识
TempName string // 模板名称
TempModel string // 模板模型
TempConfig string // 模板配置
TempStatus string // 模板状态(0未启用,1启用)
TenantId string // 租户ID
CreatedDept string // 创建部门
CreatedBy string // 创建者
CreatedAt string // 创建时间
UpdatedBy string // 更新者
UpdatedAt string // 更新时间
}
// tSimChannelTemplateColumns holds the columns for the table t_sim_channel_template.
var tSimChannelTemplateColumns = TSimChannelTemplateColumns{
Id: "id",
TempKey: "temp_key",
TempName: "temp_name",
TempModel: "temp_model",
TempConfig: "temp_config",
TempStatus: "temp_status",
TenantId: "tenant_id",
CreatedDept: "created_dept",
CreatedBy: "created_by",
CreatedAt: "created_at",
UpdatedBy: "updated_by",
UpdatedAt: "updated_at",
}
// NewTSimChannelTemplateDao creates and returns a new DAO object for table data access.
func NewTSimChannelTemplateDao(handlers ...gdb.ModelHandler) *TSimChannelTemplateDao {
return &TSimChannelTemplateDao{
group: "default",
table: "t_sim_channel_template",
columns: tSimChannelTemplateColumns,
handlers: handlers,
}
}
// DB retrieves and returns the underlying raw database management object of the current DAO.
func (dao *TSimChannelTemplateDao) DB() gdb.DB {
return g.DB(dao.group)
}
// Table returns the table name of the current DAO.
func (dao *TSimChannelTemplateDao) Table() string {
return dao.table
}
// Columns returns all column names of the current DAO.
func (dao *TSimChannelTemplateDao) Columns() TSimChannelTemplateColumns {
return dao.columns
}
// Group returns the database configuration group name of the current DAO.
func (dao *TSimChannelTemplateDao) Group() string {
return dao.group
}
// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation.
func (dao *TSimChannelTemplateDao) Ctx(ctx context.Context) *gdb.Model {
model := dao.DB().Model(dao.table)
for _, handler := range dao.handlers {
model = handler(model)
}
return model.Safe().Ctx(ctx)
}
// Transaction wraps the transaction logic using function f.
// It rolls back the transaction and returns the error if function f returns a non-nil error.
// It commits the transaction and returns nil if function f returns nil.
//
// Note: Do not commit or roll back the transaction in function f,
// as it is automatically handled by this function.
func (dao *TSimChannelTemplateDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
return dao.Ctx(ctx).Transaction(ctx, f)
}
@@ -0,0 +1,117 @@
// ==========================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// ==========================================================================
package internal
import (
"context"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
)
// TSimChannelsDao is the data access object for the table t_sim_channels.
type TSimChannelsDao struct {
table string // table is the underlying table name of the DAO.
group string // group is the database configuration group name of the current DAO.
columns TSimChannelsColumns // columns contains all the column names of Table for convenient usage.
handlers []gdb.ModelHandler // handlers for customized model modification.
}
// TSimChannelsColumns defines and stores column names for the table t_sim_channels.
type TSimChannelsColumns struct {
Id string // ID
TempKey string // 模板标识
ChanKey string // 通道编号
ChanName string // 通道名称
ChanConfig string // 通道配置
ChanStatus string // 通道状态(0未启用,1启用)
ChanAlias string // 通道别名
RegionCode string // 通道行政区域代码(省市)
AppKey string // 应用标识
AppSecret string // 应用密钥
AccessToken string // 接口调用凭证
AccessTokenCreated string // 凭证申请时间
AccessTokenExpire string // 凭证过期时间
RefreshToken string // 刷新凭证
TenantId string // 租户ID
CreatedDept string // 创建部门
CreatedBy string // 创建者
CreatedAt string // 创建时间
UpdatedBy string // 更新者
UpdatedAt string // 更新时间
}
// tSimChannelsColumns holds the columns for the table t_sim_channels.
var tSimChannelsColumns = TSimChannelsColumns{
Id: "id",
TempKey: "temp_key",
ChanKey: "chan_key",
ChanName: "chan_name",
ChanConfig: "chan_config",
ChanStatus: "chan_status",
ChanAlias: "chan_alias",
RegionCode: "region_code",
AppKey: "app_key",
AppSecret: "app_secret",
AccessToken: "access_token",
AccessTokenCreated: "access_token_created",
AccessTokenExpire: "access_token_expire",
RefreshToken: "refresh_token",
TenantId: "tenant_id",
CreatedDept: "created_dept",
CreatedBy: "created_by",
CreatedAt: "created_at",
UpdatedBy: "updated_by",
UpdatedAt: "updated_at",
}
// NewTSimChannelsDao creates and returns a new DAO object for table data access.
func NewTSimChannelsDao(handlers ...gdb.ModelHandler) *TSimChannelsDao {
return &TSimChannelsDao{
group: "default",
table: "t_sim_channels",
columns: tSimChannelsColumns,
handlers: handlers,
}
}
// DB retrieves and returns the underlying raw database management object of the current DAO.
func (dao *TSimChannelsDao) DB() gdb.DB {
return g.DB(dao.group)
}
// Table returns the table name of the current DAO.
func (dao *TSimChannelsDao) Table() string {
return dao.table
}
// Columns returns all column names of the current DAO.
func (dao *TSimChannelsDao) Columns() TSimChannelsColumns {
return dao.columns
}
// Group returns the database configuration group name of the current DAO.
func (dao *TSimChannelsDao) Group() string {
return dao.group
}
// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation.
func (dao *TSimChannelsDao) Ctx(ctx context.Context) *gdb.Model {
model := dao.DB().Model(dao.table)
for _, handler := range dao.handlers {
model = handler(model)
}
return model.Safe().Ctx(ctx)
}
// Transaction wraps the transaction logic using function f.
// It rolls back the transaction and returns the error if function f returns a non-nil error.
// It commits the transaction and returns nil if function f returns nil.
//
// Note: Do not commit or roll back the transaction in function f,
// as it is automatically handled by this function.
func (dao *TSimChannelsDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
return dao.Ctx(ctx).Transaction(ctx, f)
}
@@ -0,0 +1,22 @@
// =================================================================================
// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed.
// =================================================================================
package dao
import (
"git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/dao/internal"
)
// tSimCardInfoDao is the data access object for the table t_sim_card_info.
// You can define custom methods on it to extend its functionality as needed.
type tSimCardInfoDao struct {
*internal.TSimCardInfoDao
}
var (
// TSimCardInfo is a globally accessible object for table t_sim_card_info operations.
TSimCardInfo = tSimCardInfoDao{internal.NewTSimCardInfoDao()}
)
// Add your custom methods and functionality below.
@@ -0,0 +1,22 @@
// =================================================================================
// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed.
// =================================================================================
package dao
import (
"git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/dao/internal"
)
// tSimChannelTemplateDao is the data access object for the table t_sim_channel_template.
// You can define custom methods on it to extend its functionality as needed.
type tSimChannelTemplateDao struct {
*internal.TSimChannelTemplateDao
}
var (
// TSimChannelTemplate is a globally accessible object for table t_sim_channel_template operations.
TSimChannelTemplate = tSimChannelTemplateDao{internal.NewTSimChannelTemplateDao()}
)
// Add your custom methods and functionality below.
@@ -0,0 +1,22 @@
// =================================================================================
// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed.
// =================================================================================
package dao
import (
"git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/dao/internal"
)
// tSimChannelsDao is the data access object for the table t_sim_channels.
// You can define custom methods on it to extend its functionality as needed.
type tSimChannelsDao struct {
*internal.TSimChannelsDao
}
var (
// TSimChannels is a globally accessible object for table t_sim_channels operations.
TSimChannels = tSimChannelsDao{internal.NewTSimChannelsDao()}
)
// Add your custom methods and functionality below.
@@ -0,0 +1,259 @@
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"
"sync"
"time"
"git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/dao"
"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
}
@@ -0,0 +1,37 @@
package channelconfig
import (
"context"
"testing"
"git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/model/entity"
)
func TestChannelVersionDetectsCredentialChanges(t *testing.T) {
normal := 0
row := &channelConfigRow{IsNormal: &normal, TSimChannels: entity.TSimChannels{
ChanKey: "telecom-test", TempKey: "sim_dx_v1", AppKey: "key", AppSecret: "secret",
AccessToken: "token", RefreshToken: "refresh",
}}
config := NewChannelConfig()
before := config.getChanVersion(context.Background(), row)
for _, field := range []string{"appKey", "appSecret", "accessToken", "refreshToken", "tempKey"} {
t.Run(field, func(t *testing.T) {
changed := *row
switch field {
case "appKey":
changed.AppKey = "new-key"
case "appSecret":
changed.AppSecret = "new-secret"
case "accessToken":
changed.AccessToken = "new-token"
case "refreshToken":
changed.RefreshToken = "new-refresh"
case "tempKey":
changed.TempKey = "sim_dx_v3"
}
if got := config.getChanVersion(context.Background(), &changed); got == before {
t.Fatalf("%s change did not invalidate runtime version", field)
}
})
}
}
@@ -0,0 +1,30 @@
package channelconfig
import (
"context"
"git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/dao"
"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"
)
type sChannelUpdate struct{}
func NewChannelUpdate() *sChannelUpdate {
return &sChannelUpdate{}
}
func init() {
service.RegisterChannelUpdate(NewChannelUpdate())
}
func (s *sChannelUpdate) UpdateAccessToken(ctx context.Context, input *model.UpdateAccessTokenInput) error {
_, err := dao.TSimChannels.Ctx(ctx).Where(dao.TSimChannels.Columns().ChanKey, input.ChanKey).Update(g.Map{
dao.TSimChannels.Columns().AccessToken: input.AccessToken,
dao.TSimChannels.Columns().AccessTokenCreated: input.AccessTokenCreated,
dao.TSimChannels.Columns().AccessTokenExpire: input.AccessTokenExpire,
dao.TSimChannels.Columns().RefreshToken: input.RefreshToken,
})
return err
}
+12
View File
@@ -0,0 +1,12 @@
// ==========================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// ==========================================================================
package logic
import (
_ "git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/logic/channelconfig"
_ "git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/logic/simKeyFill"
_ "git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/logic/upstreamapi"
_ "git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/logic/upstreamimpl"
)
@@ -0,0 +1,193 @@
package simKeyFill
import (
"context"
"encoding/json"
"strings"
"git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/dao"
"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/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
)
type sSimKeyFill struct {
}
func NewSimKeyFill() *sSimKeyFill {
return &sSimKeyFill{}
}
func init() {
service.RegisterSimKeyFill(NewSimKeyFill())
}
func (s *sSimKeyFill) SimKeyFill(ctx context.Context, input *model.SimBaseReqJson) (output model.SimBaseResJson, err error) {
var operation struct {
OperationID string `json:"operationId"`
}
request := g.RequestFromCtx(ctx)
if request != nil && (request.URL.Path == "/sim/change/sim-apn" || request.URL.Path == "/sim/query/sim-apn-capability") && json.Unmarshal(input.ReqData, &operation) == nil && operation.OperationID != "" {
if len(input.SimKeys) == 0 || len(input.SimKeys) > 100 {
return nil, gerror.New("异步子任务卡数必须在1至100之间")
}
for _, key := range input.SimKeys {
if key == nil || key.ChanKey == "" || key.ICCID == "" {
return nil, gerror.New("异步子任务必须保留chanKey和ICCID快照")
}
}
return service.UpstreamAPI().SimBase(ctx, input)
}
simKeys, err := s.batchGetSimKey(ctx, input.SimKeys)
if err != nil {
return nil, err
}
if len(simKeys) == 0 {
g.Log().Debug(ctx, "SimKeyFill.batchGetSimKey, simKeys is empty, input %+v", input)
return nil, gerror.New("simKeys is empty")
}
input.SimKeys = simKeys
output, err = service.UpstreamAPI().SimBase(ctx, input)
if err != nil {
return nil, err
}
return
}
func (s *sSimKeyFill) batchGetSimKey(ctx context.Context, inputs []*model.SimKey) ([]*model.SimKey, error) {
queryICCIDKey := "sim:code:iccid:"
queryMSISDNKey := "sim:code:msisdn:"
queryIMSIKey := "sim:code:imsi:"
if len(inputs) == 0 {
g.Log().Debugf(ctx, "SimKeyFill.batchGetSimKey, The parameter is empty, inputs %+v", inputs)
return nil, gerror.New("参数不能为空")
}
var redisKeys []string
var dbKeys []string
simKeys := []*model.SimKey{}
// 设置Redis查询Key
for _, in := range inputs {
switch {
case in.ICCID != "":
key := queryICCIDKey + in.ICCID
redisKeys = append(redisKeys, key)
case in.MSISDN != "":
key := queryMSISDNKey + in.MSISDN
redisKeys = append(redisKeys, key)
case in.IMSI != "":
key := queryIMSIKey + in.IMSI
redisKeys = append(redisKeys, key)
default:
g.Log().Debugf(ctx, "SimKeyFill.batchGetSimKey, The parameter is empty, inputs %+v", inputs)
return nil, gerror.New("iccid/msisdn/imsi至少一个不能为空")
}
}
// 查询Redis数据
g.Log().Info(ctx, "SimKeyFill.batchGetSimKey, query redis")
vals, err := g.Redis().MGet(ctx, redisKeys...)
if err != nil {
g.Log().Debug(ctx, "SimKeyFill.batchGetSimKey, query redis error %+v, redisKeys %+v, inputs %+v", err, redisKeys, inputs)
} else {
// 解析Redis数据
for _, key := range redisKeys {
simKey := new(model.SimKey)
err := vals[key].Struct(&simKey)
if err != nil {
g.Log().Debug(ctx, "SimKeyFill.batchGetSimKey, unmarshalValue error %+v, key %s, inputs %+v", err, key, inputs)
dbKeys = append(dbKeys, key)
continue
}
simKeys = append(simKeys, simKey)
}
}
g.Log().Info(ctx, "SimKeyFill.batchGetSimKey, query db")
if len(dbKeys) > 0 {
// 解析数据库查询Key
var dbIccid []string
var dbMsisdn []string
var dbImsi []string
for _, key := range dbKeys {
keySplit := strings.Split(key, ":")
if len(keySplit) != 4 {
g.Log().Debug(ctx, "SimKeyFill.batchGetSimKey, Error in splicing data, key %s, keySplit %v", key, keySplit)
continue
}
switch keySplit[2] {
case "iccid":
dbIccid = append(dbIccid, keySplit[3])
case "msisdn":
dbMsisdn = append(dbMsisdn, keySplit[3])
case "imsi":
dbImsi = append(dbImsi, keySplit[3])
}
}
mod := dao.TSimCardInfo.Ctx(ctx)
if len(dbIccid) > 0 {
mod = mod.WhereOrIn(dao.TSimCardInfo.Columns().Iccid, dbIccid)
}
if len(dbMsisdn) > 0 {
mod = mod.WhereOrIn(dao.TSimCardInfo.Columns().Msisdn, dbMsisdn)
}
if len(dbImsi) > 0 {
mod = mod.WhereOrIn(dao.TSimCardInfo.Columns().Imsi, dbImsi)
}
// 查询数据库数据
var simInfos []*entity.TSimCardInfo
err = mod.Scan(&simInfos)
if err != nil {
g.Log().Error(ctx, "SimKeyFill.batchGetSimKey, query db error %+v, dbKeys %+v, inputs %+v", err, dbKeys, inputs)
return nil, gerror.New("查询数据库数据失败")
}
if len(simInfos) == 0 {
g.Log().Debug(ctx, "SimKeyFill.batchGetSimKey, simInfos is empty, dbKeys %+v, inputs %+v", dbKeys, inputs)
return nil, gerror.New("卡信息不存在")
}
// 整理卡信息
simInfoMap := make(map[string]*model.SimKey)
for _, SimInfo := range simInfos {
simInfoMap[queryICCIDKey+SimInfo.Iccid] = &model.SimKey{
ICCID: SimInfo.Iccid,
MSISDN: SimInfo.Msisdn,
IMSI: SimInfo.Imsi,
}
simInfoMap[queryMSISDNKey+SimInfo.Msisdn] = &model.SimKey{
MSISDN: SimInfo.Msisdn,
IMSI: SimInfo.Imsi,
ICCID: SimInfo.Iccid,
}
simInfoMap[queryIMSIKey+SimInfo.Imsi] = &model.SimKey{
IMSI: SimInfo.Imsi,
ICCID: SimInfo.Iccid,
MSISDN: SimInfo.Msisdn,
}
}
// 新建一个线程,进行Redis数据存储
go s.SetRedis(ctx, simInfoMap)
// 解析数据库数据
for _, info := range dbKeys {
simKeys = append(simKeys, simInfoMap[info])
}
}
return simKeys, nil
}
func (s *sSimKeyFill) SetRedis(ctx context.Context, simInfoMap map[string]*model.SimKey) {
for key, simKey := range simInfoMap {
_, err := g.Redis().Set(ctx, key, *simKey)
if err != nil {
g.Log().Debugf(ctx, "SimKeyFill.batchGetSimKey, Set error %+v, key %s, simKey %+v", err, key, simKey)
continue
}
}
}
@@ -0,0 +1,38 @@
package simKeyFill
import (
"context"
"encoding/json"
"net/http/httptest"
"testing"
"github.com/gogf/gf/v2/net/ghttp"
"git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/model"
"git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/service"
)
type snapshotTestAPI struct {
service.IUpstreamAPI
input *model.SimBaseReqJson
}
func (a *snapshotTestAPI) SimBase(_ context.Context, in *model.SimBaseReqJson) (model.SimBaseResJson, error) {
a.input = in
return nil, nil
}
func TestAsyncOperationPreservesCardSnapshot(t *testing.T) {
api := &snapshotTestAPI{}
service.RegisterUpstreamAPI(api)
defer service.RegisterUpstreamAPI(nil)
in := &model.SimBaseReqJson{SimKeys: []*model.SimKey{{ChanKey: "test", ICCID: "test-card", MSISDN: "snapshot-number"}}, ReqData: json.RawMessage(`{"operationId":"stable-id","submitOnly":true}`)}
_, err := NewSimKeyFill().SimKeyFill((&ghttp.Request{Request: httptest.NewRequest("POST", "/sim/change/sim-apn", nil)}).Context(), in)
if err != nil || api.input != in || api.input.SimKeys[0].MSISDN != "snapshot-number" {
t.Fatalf("异步请求不应重新填充或改写卡快照:%v", err)
}
}
func TestAsyncOperationRejectsIncompleteSnapshot(t *testing.T) {
_, err := NewSimKeyFill().SimKeyFill((&ghttp.Request{Request: httptest.NewRequest("POST", "/sim/change/sim-apn", nil)}).Context(), &model.SimBaseReqJson{SimKeys: []*model.SimKey{{ICCID: "test-card"}}, ReqData: json.RawMessage(`{"operationId":"stable-id"}`)})
if err == nil {
t.Fatal("缺失通道快照应在调用前拒绝")
}
}
@@ -0,0 +1,55 @@
package upstreamapi
import (
"context"
"encoding/json"
"net/http/httptest"
"strings"
"testing"
"github.com/gogf/gf/v2/net/ghttp"
"git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/model"
)
type apnPassthroughTestChannel struct{ reqData json.RawMessage }
func (c *apnPassthroughTestChannel) SimBase(_ context.Context, input *model.SimBaseReqJson) (model.SimBaseResJson, error) {
c.reqData = append(json.RawMessage(nil), input.ReqData...)
return model.SimBaseResJson{&model.SimBaseRes[json.RawMessage]{SimKey: input.SimKeys[0], Code: 0, Message: "已受理", ResData: json.RawMessage(`{"orderNum":"job-persisted","pending":true,"uncertain":false}`)}}, nil
}
func TestAPNProxyUnknownChannelPreservesUncertain(t *testing.T) {
api := NewUpstreamAPI()
out, err := api.SimBase((&ghttp.Request{Request: httptest.NewRequest("POST", "/sim/change/sim-apn", nil)}).Context(), &model.SimBaseReqJson{
SimKeys: []*model.SimKey{{ChanKey: "missing_chan", ICCID: "test"}},
ReqData: json.RawMessage(`{"operationId":"stable-id","submitOnly":true}`),
})
if err != nil || len(out) != 1 || !strings.Contains(string(out[0].ResData), `"uncertain":true`) {
t.Fatalf("未知通道不能误报业务失败: result=%+v err=%v", out, err)
}
}
func TestNonAPNProxySameNamedFieldsKeepLegacyFailure(t *testing.T) {
api := NewUpstreamAPI()
ctx := (&ghttp.Request{Request: httptest.NewRequest("POST", "/sim/change/sim-status", nil)}).Context()
out, err := api.SimBase(ctx, &model.SimBaseReqJson{SimKeys: []*model.SimKey{{ChanKey: "missing_chan", ICCID: "test"}}, ReqData: json.RawMessage(`{"operationId":"other-domain","submitOnly":true,"recoverOnly":true,"queryJobId":"unrelated"}`)})
if err != nil || len(out) != 1 || string(out[0].ResData) != `{"status":"-2"}` {
t.Fatalf("非APN同名字段改变既有失败语义:%+v %v", out, err)
}
}
func TestAPNProxyPreservesOperationAndAsyncFields(t *testing.T) {
api := NewUpstreamAPI()
instance := &apnPassthroughTestChannel{}
api.RegisterChan("test_chan", "test", &model.SimCardChannelCache{Instance: instance})
req := json.RawMessage(`{"operationId":"stable-id","submitOnly":true,"recoverOnly":false,"operType":"1"}`)
out, err := api.SimBase((&ghttp.Request{Request: httptest.NewRequest("POST", "/sim/change/sim-apn", nil)}).Context(), &model.SimBaseReqJson{SimKeys: []*model.SimKey{{ChanKey: "test_chan", ICCID: "test"}}, ReqData: req})
if err != nil || len(out) != 1 {
t.Fatalf("proxy请求错误:%v %+v", err, out)
}
if !strings.Contains(string(instance.reqData), `"operationId":"stable-id"`) {
t.Fatalf("operationId丢失:%s", instance.reqData)
}
if !strings.Contains(string(out[0].ResData), `"pending":true`) || !strings.Contains(string(out[0].ResData), `"orderNum":"job-persisted"`) {
t.Fatalf("受理标识丢失:%s", out[0].ResData)
}
}
@@ -0,0 +1,61 @@
package upstreamapi
import (
"context"
"encoding/json"
"strings"
"testing"
"git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/model"
)
type errCodeTestChannel struct {
response model.SimBaseResJson
}
func (f *errCodeTestChannel) SimBase(context.Context, *model.SimBaseReqJson) (model.SimBaseResJson, error) {
return f.response, nil
}
func TestSimBasePreservesCarrierErrCode(t *testing.T) {
const payload = `{"code":0,"message":"success","data":[{"code":9999,"message":"http status 797","errCode":"S00004","simKey":{"chanKey":"channel_003","iccid":"8986000000000000002"},"resData":{}}]}`
var upstream model.SimResponseData[model.SimBaseResJson]
if err := json.Unmarshal([]byte(payload), &upstream); err != nil {
t.Fatal(err)
}
if len(upstream.Data) != 1 {
t.Fatalf("decoded results = %d, want 1", len(upstream.Data))
}
if upstream.Data[0].ErrCode != "S00004" {
t.Fatalf("decoded errCode = %q, want S00004", upstream.Data[0].ErrCode)
}
api := NewUpstreamAPI()
api.RegisterChan("channel_003", "test", &model.SimCardChannelCache{
Instance: &errCodeTestChannel{response: upstream.Data},
})
output, err := api.SimBase(context.Background(), &model.SimBaseReqJson{
SimKeys: []*model.SimKey{{
ChanKey: "channel_003",
ICCID: "8986000000000000002",
}},
})
if err != nil {
t.Fatal(err)
}
if len(output) != 1 {
t.Fatalf("proxied results = %d, want 1", len(output))
}
if output[0].ErrCode != "S00004" {
t.Fatalf("proxied errCode = %q, want S00004", output[0].ErrCode)
}
encoded, err := json.Marshal(output)
if err != nil {
t.Fatal(err)
}
if !strings.Contains(string(encoded), `"errCode":"S00004"`) {
t.Fatalf("serialized response dropped errCode: %s", encoded)
}
}
@@ -0,0 +1,171 @@
package upstreamapi
import (
"context"
"path/filepath"
"testing"
"git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/logic/channelconfig"
"git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/model"
"git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/service"
_ "github.com/gogf/gf/contrib/drivers/sqlite/v2"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
)
type normalGateTestChannel struct {
model.SimCardChannelI
}
type normalGateTestTemplate struct {
calls int
lastConfig *model.SimChannelConfigItemCache
}
func (f *normalGateTestTemplate) NewTemp(_ context.Context, cfg *model.SimChannelConfigItemCache) model.SimCardChannelI {
f.calls++
f.lastConfig = cfg
return &normalGateTestChannel{}
}
// Exercise real ORM decoding and config versioning as well as both creation paths.
// All SQL runs against a temporary SQLite file, never a configured environment DB.
func TestUnregisterChanIsIdempotent(t *testing.T) {
api := NewUpstreamAPI()
api.RegisterChan("missing", "v1", &model.SimCardChannelCache{})
api.UnregisterChan("missing")
api.UnregisterChan("missing")
if _, ok := api.GetChan("missing"); ok {
t.Fatal("channel still registered after repeated unregister")
}
}
func TestChannelNormalCreationGate(t *testing.T) {
ctx := context.Background()
if err := gdb.SetConfigGroup("default", gdb.ConfigGroup{{
Type: "sqlite", Name: filepath.Join(t.TempDir(), "channels.db"),
}}); err != nil {
t.Fatal(err)
}
db := g.DB()
t.Cleanup(func() { _ = db.Close(ctx) })
execSQL := func(query string, args ...any) {
t.Helper()
if _, err := db.Exec(ctx, query, args...); err != nil {
t.Fatal(err)
}
}
execSQL("CREATE TABLE t_sim_channels (id INTEGER PRIMARY KEY, chan_key TEXT, temp_key TEXT, chan_name TEXT, is_normal INTEGER NULL)")
execSQL("CREATE TABLE t_sim_channel_template (id INTEGER PRIMARY KEY, temp_key TEXT, temp_model TEXT)")
execSQL("INSERT INTO t_sim_channel_template (id, temp_key, temp_model) VALUES (1, 'gate_test', 'direct')")
oldConfig := service.ChannelConfig()
t.Cleanup(func() { service.RegisterChannelConfig(oldConfig) })
for _, path := range []string{"loop", "refresh"} {
t.Run(path, func(t *testing.T) {
for _, tc := range []struct {
name string
value any
eligible bool
}{
{"yes_is_zero", 0, true},
{"no_is_one", 1, false},
{"null", nil, false},
{"unexpected", 2, false},
} {
t.Run(tc.name, func(t *testing.T) {
execSQL("DELETE FROM t_sim_channels")
execSQL("INSERT INTO t_sim_channels (id, chan_key, temp_key, is_normal) VALUES (1, 'gate_channel', 'gate_test', ?)", tc.value)
config := channelconfig.NewChannelConfig()
service.RegisterChannelConfig(config)
api := NewUpstreamAPI()
factory := &normalGateTestTemplate{}
api.RegisterTemp("gate_test", factory)
reload := func() {
t.Helper()
var err error
if path == "loop" {
err = config.LoadAllChannelConfig(ctx)
if err == nil {
err = api.loop(ctx)
}
} else {
err = api.RefreshChannel(ctx, "gate_channel")
}
if err != nil {
t.Fatalf("refresh must succeed: %v", err)
}
}
reload()
cfg, ok := config.GetChannel("gate_channel")
if !ok || cfg.CanCreateInstance() != tc.eligible {
t.Fatalf("wrong eligibility after ORM scan: %+v", cfg)
}
if tc.value == nil && cfg.IsNormal != nil {
t.Fatalf("SQL NULL decoded as non-nil: %v", *cfg.IsNormal)
}
if cfg.Channel.ChanKey != "gate_channel" {
t.Fatal("embedded channel fields were not scanned")
}
_, exists := api.GetChan("gate_channel")
if exists != tc.eligible || (factory.calls == 1) != tc.eligible {
t.Fatalf("instance exists=%v factory calls=%d eligible=%v", exists, factory.calls, tc.eligible)
}
initialVersion := cfg.Version
// Only is_normal changes: the configuration cache must notice it.
execSQL("UPDATE t_sim_channels SET is_normal=0")
reload()
current, ok := api.GetChan("gate_channel")
if !ok || current.Instance == nil {
t.Fatal("eligible channel was not instantiated")
}
cfg, _ = config.GetChannel("gate_channel")
if !tc.eligible && cfg.Version == initialVersion {
t.Fatal("changing only is_normal did not change config version")
}
oldCalls := factory.calls
// Changing only is_normal must evict the old runtime instance.
execSQL("UPDATE t_sim_channels SET is_normal=1")
reload()
if _, exists := api.GetChan("gate_channel"); exists || factory.calls != oldCalls {
t.Fatal("ineligible channel retained or recreated its runtime instance")
}
cfg, _ = config.GetChannel("gate_channel")
if cfg.CanCreateInstance() || cfg.Version == current.Version {
t.Fatal("ineligible configuration was not cached with a new version")
}
// Disabled refreshes remain successful even if the template disappears.
execSQL("UPDATE t_sim_channels SET chan_name='changed while disabled', temp_key='missing_template'")
reload()
reload()
if _, exists := api.GetChan("gate_channel"); exists || factory.calls != oldCalls {
t.Fatal("repeated ineligible refresh was not an idempotent removal")
}
// A process restart cannot recreate the disabled instance.
restarted := NewUpstreamAPI()
restarted.RegisterTemp("gate_test", factory)
if err := restarted.loop(ctx); err != nil {
t.Fatal(err)
}
if _, ok := restarted.GetChan("gate_channel"); ok {
t.Fatal("reload recreated disabled instance")
}
execSQL("UPDATE t_sim_channels SET is_normal=0, temp_key='gate_test'")
reload()
restored, exists := api.GetChan("gate_channel")
if !exists || restored == current || factory.calls != oldCalls+1 ||
factory.lastConfig.Channel.ChanName != "changed while disabled" {
t.Fatal("restored channel did not rebuild using the latest configuration")
}
})
}
})
}
}
@@ -0,0 +1,399 @@
package upstreamapi
import (
"context"
"encoding/json"
"strings"
"sync"
"time"
"git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/consts"
"git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/dao"
"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/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
)
var REDIS_KEY = "sim:chan:iccid:"
type sUpstreamAPI struct {
tempMap map[string]model.SimCardTempI
chanMap map[string]*model.SimCardChannelCache
channelMux sync.Mutex
channelOps map[string]*sync.Mutex
sync.RWMutex
running bool
}
func NewUpstreamAPI() *sUpstreamAPI {
return &sUpstreamAPI{
chanMap: make(map[string]*model.SimCardChannelCache),
tempMap: make(map[string]model.SimCardTempI),
channelOps: make(map[string]*sync.Mutex),
}
}
func init() {
service.RegisterUpstreamAPI(NewUpstreamAPI())
}
func (u *sUpstreamAPI) Start(ctx context.Context) error {
u.running = true
lastReloadTime := int64(0)
go func() {
for u.running {
now := time.Now().Unix()
reloadInterval := g.Cfg().MustGet(ctx, "upstreamapi.reload_interval", 60).Int64()
if now-lastReloadTime >= reloadInterval {
lastReloadTime = now
err := u.loop(ctx)
if err != nil {
g.Log().Errorf(ctx, "sUpstreamAPI loop err %+v", err)
}
}
time.Sleep(1 * time.Second)
}
}()
g.Log().Infof(ctx, "sUpstreamAPI Start success")
return nil
}
func (u *sUpstreamAPI) Stop(ctx context.Context) error {
u.running = false
return nil
}
func (u *sUpstreamAPI) RefreshChannel(ctx context.Context, chanKey string) error {
unlock := u.lockChannel(chanKey)
defer unlock()
return u.refreshChannelLocked(ctx, chanKey)
}
func (u *sUpstreamAPI) refreshChannelLocked(ctx context.Context, chanKey string) error {
if err := service.ChannelConfig().RefreshChannel(ctx, chanKey); err != nil {
return err
}
channelConf, ok := service.ChannelConfig().GetChannel(chanKey)
if !ok || channelConf == nil {
return gerror.Newf("channel config not found: %s", chanKey)
}
if !channelConf.CanCreateInstance() {
u.UnregisterChan(chanKey)
g.Log().Infof(ctx, "channel instance removed because is_normal is not 0, chanKey: %s", chanKey)
return nil
}
temp, ok := u.GetTemp(channelConf.Template.TempKey)
if !ok || temp == nil {
return gerror.Newf("channel template not registered: %s", channelConf.Template.TempKey)
}
instance := temp.NewTemp(ctx, channelConf)
if instance == nil {
return gerror.Newf("create channel instance failed: %s", chanKey)
}
u.RegisterChan(chanKey, channelConf.Version, &model.SimCardChannelCache{
Instance: instance,
Version: channelConf.Version,
})
g.Log().Infof(ctx, "channel refreshed, chanKey: %s, version: %s", chanKey, channelConf.Version)
return nil
}
// RemoveChannel drops the cached configuration and runtime instance for a
// channel. Deleting an already absent key is a successful no-op.
func (u *sUpstreamAPI) RemoveChannel(ctx context.Context, chanKey string) error {
chanKey = strings.TrimSpace(chanKey)
if chanKey == "" {
return gerror.New("chanKey is required")
}
unlock := u.lockChannel(chanKey)
defer unlock()
if err := service.ChannelConfig().RemoveChannel(ctx, chanKey); err != nil {
return err
}
u.UnregisterChan(chanKey)
g.Log().Infof(ctx, "channel removed, chanKey: %s", chanKey)
return nil
}
func (u *sUpstreamAPI) loop(ctx context.Context) error {
channelConfigs, err := service.ChannelConfig().GetAllChannel(ctx)
if err != nil {
return err
}
for _, snapshot := range channelConfigs {
if snapshot == nil || snapshot.Channel == nil {
continue
}
chanKey := snapshot.Channel.ChanKey
func() {
unlock := u.lockChannel(chanKey)
defer unlock()
if err := service.ChannelConfig().RefreshChannel(ctx, chanKey); err != nil {
g.Log().Errorf(ctx, "reload channel config failed, chanKey: %s, err: %+v", chanKey, err)
return
}
channelConf, ok := service.ChannelConfig().GetChannel(chanKey)
if !ok || channelConf == nil {
return
}
if !channelConf.CanCreateInstance() {
u.UnregisterChan(chanKey)
return
}
tempKey := channelConf.Template.TempKey
channelCache, ok := u.GetChan(chanKey)
if ok && channelCache != nil && channelCache.Version == channelConf.Version {
return
}
isNew := !ok || channelCache == nil
g.Log().Infof(ctx, "Create channel instance begin, chanKey: %s, isNew: %v", chanKey, isNew)
temp, ok := u.GetTemp(tempKey)
if !ok || temp == nil {
g.Log().Errorf(ctx, "Create channel instance failed, Temp not found, tempKey: %s", tempKey)
return
}
instance := temp.NewTemp(ctx, channelConf)
if instance == nil {
g.Log().Errorf(ctx, "Create channel instance failed, Temp new instance failed, tempKey: %s", tempKey)
return
}
channelCache = &model.SimCardChannelCache{Instance: instance, Version: channelConf.Version}
u.RegisterChan(chanKey, channelConf.Version, channelCache)
g.Log().Infof(ctx, "Create channel instance success, chanKey: %s, version: %s", chanKey, channelConf.Version)
}()
}
return nil
}
func (u *sUpstreamAPI) lockChannel(chanKey string) func() {
u.channelMux.Lock()
lock := u.channelOps[chanKey]
if lock == nil {
lock = &sync.Mutex{}
u.channelOps[chanKey] = lock
}
u.channelMux.Unlock()
lock.Lock()
return lock.Unlock
}
func (u *sUpstreamAPI) UnregisterChan(chanKey string) {
u.Lock()
delete(u.chanMap, chanKey)
u.Unlock()
}
func (u *sUpstreamAPI) RegisterChan(chanKey string, version string, channel *model.SimCardChannelCache) {
u.Lock()
defer u.Unlock()
u.chanMap[chanKey] = channel
}
func (u *sUpstreamAPI) removeChan(chanKey string) {
u.Lock()
defer u.Unlock()
delete(u.chanMap, chanKey)
}
func (u *sUpstreamAPI) RegisterTemp(tempKey string, channel model.SimCardTempI) {
u.Lock()
defer u.Unlock()
u.tempMap[tempKey] = channel
}
func (u *sUpstreamAPI) GetTemp(tempKey string) (model.SimCardTempI, bool) {
u.RLock()
defer u.RUnlock()
temp, ok := u.tempMap[tempKey]
return temp, ok
}
func (u *sUpstreamAPI) GetChan(chanKey string) (*model.SimCardChannelCache, bool) {
u.RLock()
defer u.RUnlock()
channel, ok := u.chanMap[chanKey]
return channel, ok
}
func (u *sUpstreamAPI) packChanKey(ctx context.Context, simKeys []*model.SimKey) (map[string][]*model.SimKey, error) {
chanKeyMap := make(map[string][]*model.SimKey)
simKeyMap := make(map[string]*model.SimKey, len(simKeys))
unknownChanKeyIccids := make([]string, 0)
queryRedisIccidKeys := make([]string, 0)
queryDbIccids := make([]string, 0)
// 检查通道id是都携带
for _, simKey := range simKeys {
simKeyMap[simKey.ICCID] = simKey
if simKey.ChanKey == "" {
unknownChanKeyIccids = append(unknownChanKeyIccids, simKey.ICCID)
queryRedisIccidKeys = append(queryRedisIccidKeys, REDIS_KEY+simKey.ICCID)
} else {
chanKeyMap[simKey.ChanKey] = append(chanKeyMap[simKey.ChanKey], simKey)
}
}
if len(unknownChanKeyIccids) == 0 {
return chanKeyMap, nil
}
allIccid := make(map[string]bool)
for _, iccid := range unknownChanKeyIccids {
allIccid[iccid] = false
}
// TODO: 临时数据库查询,后面添加内存缓存和Redis缓存
// 从Redis中查询通道id
redisChanKeys, err := g.Redis().MGet(ctx, queryRedisIccidKeys...)
if err != nil {
g.Log().Error(ctx, "sUpstreamAPI.packChanKey, query redis error %+v, queryRedisIccidKeys %+v", err, queryRedisIccidKeys)
return nil, gerror.New("查询Redis通道id失败")
}
for iccidKey, chanKey := range redisChanKeys {
iccidSplit := strings.Split(iccidKey, ":")
if len(iccidSplit) != 4 {
g.Log().Debug(ctx, "sUpstreamAPI.packChanKey, iccidKey format error, iccidKey:%s", iccidKey)
continue
}
iccid := iccidSplit[3]
chanKeyStr := chanKey.String()
if chanKeyStr == "" {
// 查询redis为空
g.Log().Debug(ctx, "sUpstreamAPI.packChanKey, query redis empty, iccid:%s, simKeys:%v", iccid, simKeys)
queryDbIccids = append(queryDbIccids, iccid)
continue
}
allIccid[iccid] = true
simKey := simKeyMap[iccid]
if simKey == nil {
g.Log().Debug(ctx, "sUpstreamAPI.packChanKey, simKey not found, iccid:%s, simKeys:%v", iccid, simKeys)
continue
}
chanKeyMap[chanKeyStr] = append(chanKeyMap[chanKeyStr], &model.SimKey{
ChanKey: chanKeyStr,
ICCID: iccid,
IMSI: simKey.IMSI,
MSISDN: simKey.MSISDN,
})
}
if len(queryDbIccids) == 0 {
return chanKeyMap, nil
}
var simCardInfos []*entity.TSimCardInfo
g.Log().Info(ctx, "sUpstreamAPI.packChanKey, query db, %v", queryDbIccids)
err = dao.TSimCardInfo.Ctx(ctx).
Fields(dao.TSimCardInfo.Columns().ChanKey, dao.TSimCardInfo.Columns().Iccid).
WhereIn(dao.TSimCardInfo.Columns().Iccid, queryDbIccids).Scan(&simCardInfos)
if err != nil {
g.Log().Error(ctx, "sUpstreamAPI.packChanKey, query db error %+v, queryDbIccids %+v, simKeys %+v", err, queryDbIccids, simKeys)
return nil, gerror.New("查询数据库数据失败")
}
for _, simCardInfo := range simCardInfos {
allIccid[simCardInfo.Iccid] = true
simKey, _ := simKeyMap[simCardInfo.Iccid]
chanKeyMap[simCardInfo.ChanKey] = append(chanKeyMap[simCardInfo.ChanKey], &model.SimKey{
ChanKey: simCardInfo.ChanKey,
ICCID: simCardInfo.Iccid,
IMSI: simKey.IMSI,
MSISDN: simKey.MSISDN,
})
}
// 异步写入Redis
go u.SetRedis(ctx, simCardInfos)
for iccid, found := range allIccid {
if !found {
chanKeyMap[""] = append(chanKeyMap[""], &model.SimKey{
ChanKey: "",
ICCID: iccid,
})
}
}
return chanKeyMap, nil
}
// 物联网卡
func (u *sUpstreamAPI) SimBase(ctx context.Context, input *model.SimBaseReqJson) (model.SimBaseResJson, error) {
output := make([]*model.SimBaseRes[json.RawMessage], 0)
inputMap := make(map[string]*model.SimBaseReqJson)
// 提交/恢复/查询阶段的代理异常不是运营商办理失败。
failureResData := json.RawMessage(`{"status":"-2"}`)
var apnRequest struct {
SubmitOnly bool `json:"submitOnly"`
RecoverOnly bool `json:"recoverOnly"`
QueryJobID string `json:"queryJobId"`
}
request := g.RequestFromCtx(ctx)
if request != nil && request.URL.Path == "/sim/change/sim-apn" && json.Unmarshal(input.ReqData, &apnRequest) == nil && (apnRequest.SubmitOnly || apnRequest.RecoverOnly || apnRequest.QueryJobID != "") {
failureResData = json.RawMessage(`{"uncertain":true}`)
}
chanKeyMap, err := u.packChanKey(ctx, input.SimKeys)
if err != nil {
return nil, err
}
for chanKey, simKeys := range chanKeyMap {
inputMap[chanKey] = &model.SimBaseReqJson{
SimKeys: simKeys,
ReqData: input.ReqData,
}
}
for chanKey, inputNew := range inputMap {
if chanKey == "" {
for _, simKey := range inputNew.SimKeys {
output = append(output, &model.SimBaseRes[json.RawMessage]{
SimKey: simKey,
Code: consts.ApiStatusError,
Message: "通道未配置",
ResData: failureResData,
})
}
continue
}
channel, ok := u.GetChan(chanKey)
if !ok || channel.Instance == nil {
for _, simKey := range inputNew.SimKeys {
output = append(output, &model.SimBaseRes[json.RawMessage]{
SimKey: simKey,
Code: consts.ApiStatusError,
Message: "通道未实例化",
ResData: failureResData,
})
}
continue
}
out, err := channel.Instance.SimBase(ctx, inputNew)
if err != nil {
for _, simKey := range inputNew.SimKeys {
output = append(output, &model.SimBaseRes[json.RawMessage]{
SimKey: simKey,
Code: consts.ApiStatusError,
Message: err.Error(),
ResData: failureResData,
})
}
} else {
for _, item := range out {
output = append(output, &model.SimBaseRes[json.RawMessage]{
SimKey: item.SimKey,
Code: item.Code,
Message: item.Message,
ErrCode: item.ErrCode,
ResData: item.ResData,
})
}
}
}
return output, nil
}
// 异步写入redis
func (u *sUpstreamAPI) SetRedis(ctx context.Context, simCardInfos []*entity.TSimCardInfo) {
for _, simCardInfo := range simCardInfos {
_, err := g.Redis().Set(ctx, REDIS_KEY+simCardInfo.Iccid, simCardInfo.ChanKey)
if err != nil {
g.Log().Debugf(ctx, "sUpstreamAPI.SetRedis, Set Redis error %+v, key %s, simCardInfo %+v", err, REDIS_KEY+simCardInfo.Iccid, simCardInfo)
continue
}
}
}
@@ -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,117 @@
package upstreamimpl
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/os/gcfg"
"git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/logic/upstreamapi"
"git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/model"
)
func TestAPNProxyHTTPFailureIsUncertainOnlyOnAPNRoute(t *testing.T) {
oldAdapter := g.Cfg().GetAdapter()
defer g.Cfg().SetAdapter(oldAdapter)
for _, tc := range []struct {
name, path, body string
disconnect, uncertain bool
}{
{"HTTP200顶层9999", "/sim/change/sim-apn", `{"code":9999,"message":"模拟网关未确认"}`, false, true},
{"通道连接断开", "/sim/change/sim-apn", "", true, true},
{"非APN同名字段", "/sim/change/sim-status", `{"code":9999,"message":"模拟非APN网关错误"}`, false, false},
} {
t.Run(tc.name, func(t *testing.T) {
chanServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if tc.disconnect {
conn, _, err := w.(http.Hijacker).Hijack()
if err != nil {
t.Error(err)
return
}
_ = conn.Close()
return
}
_, _ = w.Write([]byte(tc.body))
}))
defer chanServer.Close()
adapter, err := gcfg.NewAdapterContent(`{"gateway":{"address":"` + strings.TrimPrefix(chanServer.URL, "http://") + `","timeout":"1s"}}`)
if err != nil {
t.Fatal(err)
}
g.Cfg().SetAdapter(adapter)
api := upstreamapi.NewUpstreamAPI()
api.RegisterChan("fake-channel", "test", &model.SimCardChannelCache{Instance: &sSimYD{}})
proxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var input model.SimBaseReqJson
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
t.Error(err)
return
}
ctx := (&ghttp.Request{Request: r}).Context()
items, err := api.SimBase(ctx, &input)
if err != nil {
t.Error(err)
return
}
_ = json.NewEncoder(w).Encode(map[string]any{"code": 0, "data": items})
}))
defer proxy.Close()
body := []byte(`{"simKeys":[{"chanKey":"fake-channel","iccid":"fake-card"}],"reqData":{"operationId":"fake-operation","submitOnly":true}}`)
response, err := http.Post(proxy.URL+tc.path, "application/json", bytes.NewReader(body))
if err != nil {
t.Fatal(err)
}
defer response.Body.Close()
var output struct {
Code int `json:"code"`
Data model.SimBaseResJson `json:"data"`
}
if err := json.NewDecoder(response.Body).Decode(&output); err != nil {
t.Fatal(err)
}
if response.StatusCode != 200 || output.Code != 0 || len(output.Data) != 1 || output.Data[0].Code != 9999 || output.Data[0].Message == "" {
t.Fatalf("代理异常响应无效:HTTP=%d result=%+v", response.StatusCode, output)
}
isUncertain := strings.Contains(string(output.Data[0].ResData), `"uncertain":true`)
if isUncertain != tc.uncertain {
t.Fatalf("APN路由隔离失效:%s", output.Data[0].ResData)
}
})
}
}
func TestAPNProxyHTTPKeepsDurableJobAndFrozenRequest(t *testing.T) {
oldAdapter := g.Cfg().GetAdapter()
defer g.Cfg().SetAdapter(oldAdapter)
var captured model.SimBaseReqJson
chanServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if err := json.NewDecoder(r.Body).Decode(&captured); err != nil {
t.Error(err)
return
}
_, _ = w.Write([]byte(`{"code":0,"data":[{"code":0,"simKey":{"chanKey":"fake-channel","iccid":"fake-card","msisdn":"frozen-number"},"resData":{"orderNum":"saved-job","pending":true}}]}`))
}))
defer chanServer.Close()
adapter, err := gcfg.NewAdapterContent(`{"gateway":{"address":"` + strings.TrimPrefix(chanServer.URL, "http://") + `","timeout":"1s"}}`)
if err != nil {
t.Fatal(err)
}
g.Cfg().SetAdapter(adapter)
api := upstreamapi.NewUpstreamAPI()
api.RegisterChan("fake-channel", "test", &model.SimCardChannelCache{Instance: &sSimYD{}})
input := &model.SimBaseReqJson{SimKeys: []*model.SimKey{{ChanKey: "fake-channel", ICCID: "fake-card", MSISDN: "frozen-number"}}, ReqData: json.RawMessage(`{"operationId":"stable-operation","recoverOnly":true}`)}
ctx := (&ghttp.Request{Request: httptest.NewRequest("POST", "/sim/change/sim-apn", nil)}).Context()
items, err := api.SimBase(ctx, input)
if err != nil || len(items) != 1 || !strings.Contains(string(items[0].ResData), `"orderNum":"saved-job"`) || !strings.Contains(string(items[0].ResData), `"pending":true`) {
t.Fatalf("HTTP转发丢失jobId:%+v %v", items, err)
}
if string(captured.ReqData) != string(input.ReqData) || captured.SimKeys[0].MSISDN != "frozen-number" {
t.Fatalf("恢复请求被改写:%+v", captured)
}
}
@@ -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,97 @@
package upstreamimpl
import (
"context"
"encoding/json"
"net/http"
"time"
"fmt"
"github.com/gogf/gf/v2/frame/g"
"strings"
"git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/model"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/net/gclient"
)
const (
// gatewayBatchTimeoutConfigKey 限制单次批量转发的最长等待,防止上游异常时无限占用连接。
gatewayBatchTimeoutConfigKey = "gateway.batchTimeout"
// defaultGatewayBatchTimeout 覆盖 100 张卡按单卡 10 秒预算的最坏耗时,并留有余量。
defaultGatewayBatchTimeout = 20 * time.Minute
// defaultDataServiceTimeout 是数据开停转发的独立预算。
defaultDataServiceTimeout = 210 * time.Second
)
// configureGatewayTimeout 让响应头等待与整次请求使用同一预算,避免默认30秒提前中断。
// upstream-chan 对批量操作按卡串行调用运营商接口,耗时随卡数线性增长;
// 因此批量请求不能沿用 gclient 默认的 30 秒响应头超时,否则请求仍在办理就会被判为失败。
func configureGatewayTimeout(ctx context.Context, client *gclient.Client, path string, count int, perCard time.Duration) {
if path == "/sim/change/sim-apn" || path == "/sim/query/sim-apn-capability" {
client.SetContentType("application/json")
}
applyGatewayTimeout(client, path, count, perCard,
configDuration(ctx, "gateway.dataServiceTimeout", defaultDataServiceTimeout),
configDuration(ctx, gatewayBatchTimeoutConfigKey, defaultGatewayBatchTimeout))
}
// applyGatewayTimeout 计算并应用单次转发预算:整次请求与响应头等待必须一致,
// 否则响应头超时会在业务仍在办理时提前中断请求。
func applyGatewayTimeout(client *gclient.Client, path string, count int, perCard, dataServiceTimeout, batchTimeout time.Duration) {
if count < 1 {
count = 1
}
if perCard <= 0 {
perCard = 10 * time.Second
}
timeout := perCard * time.Duration(count)
if path == "/sim/change/sim-apn" {
timeout = dataServiceTimeout
if timeout <= 0 {
timeout = defaultDataServiceTimeout
}
} else if batchTimeout > 0 && timeout > batchTimeout {
timeout = batchTimeout
}
client.SetTimeout(timeout)
if transport, ok := client.Transport.(*http.Transport); ok {
transport.ResponseHeaderTimeout = timeout
}
}
// configDuration 读取时长配置,兼容写成 "20m" 字符串或秒数整数两种形式。
func configDuration(ctx context.Context, key string, fallback time.Duration) time.Duration {
value := g.Cfg().MustGet(ctx, key)
if value == nil || value.IsNil() {
return fallback
}
if duration := value.Duration(); duration >= time.Second {
return duration
}
if seconds := value.Int(); seconds > 0 {
return time.Duration(seconds) * time.Second
}
return fallback
}
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,97 @@
package upstreamimpl
import (
"context"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/gogf/gf/v2/frame/g"
)
func TestGatewayTimeoutAllowsDelayedHeaders(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
time.Sleep(40 * time.Millisecond)
_, _ = w.Write([]byte(`{"code":0,"data":[]}`))
}))
defer server.Close()
client := g.Client()
other := g.Client()
original := other.Transport.(*http.Transport)
original.ResponseHeaderTimeout = time.Millisecond
client.Transport.(*http.Transport).ResponseHeaderTimeout = time.Millisecond
configureGatewayTimeout(context.Background(), client, "/sim/change/sim-apn", 1, time.Second)
response, err := client.Post(context.Background(), server.URL, nil)
if err != nil {
t.Fatal(err)
}
defer response.Close()
if original.ResponseHeaderTimeout != time.Millisecond {
t.Fatal("修改了共享Transport")
}
}
func TestNonAPNGatewayTimeoutPreservesHeaderBudget(t *testing.T) {
client := g.Client()
transport := client.Transport.(*http.Transport)
transport.ResponseHeaderTimeout = 17 * time.Second
configureGatewayTimeout(context.Background(), client, "/sim/query/test", 2, 3*time.Second)
if client.Client.Timeout != 6*time.Second || transport.ResponseHeaderTimeout != 6*time.Second {
t.Fatalf("非APN响应头预算必须与请求预算一致:request=%s header=%s", client.Client.Timeout, transport.ResponseHeaderTimeout)
}
}
// TestNonAPNBatchExpandsHeaderBudget 覆盖批量停复机等非 APN 路径:
// 50 张卡按单卡预算放大后,响应头等待不能继续停留在 gclient 的 30 秒默认值。
func TestNonAPNBatchExpandsHeaderBudget(t *testing.T) {
client := g.Client()
transport := client.Transport.(*http.Transport)
transport.ResponseHeaderTimeout = 30 * time.Second
applyGatewayTimeout(client, "/sim/change/sim-status", 50, 10*time.Second, defaultDataServiceTimeout, defaultGatewayBatchTimeout)
if client.Client.Timeout != 500*time.Second {
t.Fatalf("请求预算=%s,期望500s", client.Client.Timeout)
}
if transport.ResponseHeaderTimeout != client.Client.Timeout {
t.Fatalf("响应头预算=%s与请求预算不一致", transport.ResponseHeaderTimeout)
}
}
// TestNonAPNBatchRespectsBatchTimeoutCap 保证异常上游不会无限占用连接:
// 超出批量上限时按上限截断,且响应头预算同步截断。
func TestNonAPNBatchRespectsBatchTimeoutCap(t *testing.T) {
client := g.Client()
transport := client.Transport.(*http.Transport)
applyGatewayTimeout(client, "/sim/query/usage", 1000, 10*time.Second, defaultDataServiceTimeout, defaultGatewayBatchTimeout)
if client.Client.Timeout != defaultGatewayBatchTimeout {
t.Fatalf("请求预算=%s,期望批量上限%s", client.Client.Timeout, defaultGatewayBatchTimeout)
}
if transport.ResponseHeaderTimeout != defaultGatewayBatchTimeout {
t.Fatalf("响应头预算=%s,期望批量上限%s", transport.ResponseHeaderTimeout, defaultGatewayBatchTimeout)
}
}
// TestNonAPNSingleCardKeepsPerCardBudget 保证单卡请求不被放大成批量预算。
func TestNonAPNSingleCardKeepsPerCardBudget(t *testing.T) {
for _, count := range []int{0, 1} {
client := g.Client()
transport := client.Transport.(*http.Transport)
applyGatewayTimeout(client, "/sim/query/test", count, 10*time.Second, defaultDataServiceTimeout, defaultGatewayBatchTimeout)
if client.Client.Timeout != 10*time.Second || transport.ResponseHeaderTimeout != 10*time.Second {
t.Fatalf("count=%d request=%s header=%s", count, client.Client.Timeout, transport.ResponseHeaderTimeout)
}
}
}
func TestDataServiceTimeoutOverridesCardBudget(t *testing.T) {
for _, count := range []int{0, 1, 100} {
client := g.Client()
configureGatewayTimeout(context.Background(), client, "/sim/change/sim-apn", count, time.Millisecond)
if client.Client.Timeout != 210*time.Second {
t.Fatalf("count=%d timeout=%s", count, client.Client.Timeout)
}
if client.Transport.(*http.Transport).ResponseHeaderTimeout != client.Client.Timeout {
t.Fatal("响应头预算不一致")
}
}
}
@@ -0,0 +1,77 @@
package upstreamimpl_test
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"reflect"
"strings"
"testing"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/os/gcfg"
_ "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 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"
"regexp"
"time"
"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"
"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())
configureGatewayTimeout(ctx, client, path, len(input.SimKeys), s.getUpstreamTimeout(ctx))
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"
"sync/atomic"
"time"
"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/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())
configureGatewayTimeout(ctx, client, path, len(input.SimKeys), s.getUpstreamTimeout(ctx))
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.Wrap(err, "请求通道失败")
}
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)
}
})
}
}
+27
View File
@@ -0,0 +1,27 @@
// =================================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// =================================================================================
package do
import (
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
)
// TApi is the golang structure of table t_api for DAO operations like Where/Data.
type TApi struct {
g.Meta `orm:"table:t_api, do:true"`
Id any // ID
AppKey any // APPKey
AppSecret any // APPSecret
RateLimitPeriod any // 限流周期(秒,0:不限流)
RateLimitCount any // 限流次数
DeptId any // 所属机构
TenantId any // 租户ID
CreatedDept any // 创建部门
CreatedBy any // 创建者
CreatedAt *gtime.Time // 创建时间
UpdatedBy any // 更新者
UpdatedAt *gtime.Time // 更新时间
}
@@ -0,0 +1,35 @@
// =================================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// =================================================================================
package do
import (
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
)
// TCustomer is the golang structure of table t_customer for DAO operations like Where/Data.
type TCustomer struct {
g.Meta `orm:"table:t_customer, do:true"`
Id any // ID
CKey any // 客户编号
CType any // 客户类型(0:企业,1:个人)
CName any // 客户名称
CShortName any // 客户简称
ContactUserName any // 联系人
ContactPhone any // 联系电话
LicenseNumber any // 统一社会信用代码
RegionCode any // 行政区域代码
Address any // 地址
DepositAmount any // 预存金额
CreditAmountTotal any // 已授信额度
CreditAmountUsed any // 已使用授信额度
DeptId any // 所属机构
TenantId any // 租户ID
CreatedDept any // 创建部门
CreatedBy any // 创建者
CreatedAt *gtime.Time // 创建时间
UpdatedBy any // 更新者
UpdatedAt *gtime.Time // 更新时间
}
@@ -0,0 +1,34 @@
// =================================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// =================================================================================
package do
import (
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
)
// TSimAgentPackage is the golang structure of table t_sim_agent_package for DAO operations like Where/Data.
type TSimAgentPackage struct {
g.Meta `orm:"table:t_sim_agent_package, do:true"`
Id any // ID
PackageKey any // 套餐分类编码
PackageName any // 套餐分类名称
ParentPackageKey any // 上级套餐分类编码
PackageStatus any // 是否启用(0:未启用,1启用)
BillingMethod any // 计费方式(0:月,1:季,2:年)
ChannelPrice any // 渠道价格
MonthlyFee any // 月租费
PackageDataMb any // 套餐流量(MB)
PackageDataGb any // 套餐流量(GB)
ValidityPeriod any // 有效期
Description any // 描述信息
OrderNum any // 排序
TenantId any // 租户ID
CreatedDept any // 创建部门
CreatedBy any // 创建者
CreatedAt *gtime.Time // 创建时间
UpdatedBy any // 更新者
UpdatedAt *gtime.Time // 更新时间
}
@@ -0,0 +1,29 @@
// =================================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// =================================================================================
package do
import (
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
)
// TSimAgentPacket is the golang structure of table t_sim_agent_packet for DAO operations like Where/Data.
type TSimAgentPacket struct {
g.Meta `orm:"table:t_sim_agent_packet, do:true"`
Id any // ID
PackageKey any // 套餐分类编码
PacketKey any // 套餐编码
PacketName any // 套餐名称
ValidityPeriod any // 套餐有效期(月份)
PacketStatus any // 是否启用(0:未启用,1启用)
DiscountPercent any // 折扣率(百分比)
Description any // 描述信息
TenantId any // 租户ID
CreatedDept any // 创建部门
CreatedBy any // 创建者
CreatedAt *gtime.Time // 创建时间
UpdatedBy any // 更新者
UpdatedAt *gtime.Time // 更新时间
}
@@ -0,0 +1,73 @@
// =================================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// =================================================================================
package do
import (
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
)
// TSimCardInfo is the golang structure of table t_sim_card_info for DAO operations like Where/Data.
type TSimCardInfo struct {
g.Meta `orm:"table:t_sim_card_info, do:true"`
Id any // ID
Msisdn any // SIM号码
Iccid any // SIM卡号
Imsi any // SIM识别码
Imei any // IMEI
OpenDate *gtime.Time // 开卡日期(运营商)
ActivateDate *gtime.Time // 激活时间(运营商)
AllocationBatch any // 划拨批次
AllocationDate *gtime.Time // 划拨日期
ChanKey any // 通道编号
IsPool any // 是否流量池(0:否,1:是)
PoolKey any // 流量池编号
BatchKey any // 入库批次
BatchDate *gtime.Time // 入库日期
PackageKey any // 套餐分类编码
CurrPacketKey any // 当前套餐编码
AllowPacketChange any // 是否允许更改套餐(0:否 1:是)
CardStatus any // 卡状态
ValidityPeriod any // 有效期(月份)
ExpiryDate *gtime.Time // 套餐到期日期
UpstreamExpiryDate *gtime.Time // 上游到期时间(运营商)
IpType any // IP版本(人工录入)
NetModel any // 网络模式(人工录入)
RegionCode any // 行政区域代码(省市)
HasOffNet any // 是否开启达量断网
OffNetQuota any // 达量断网阈值(MB)
OffNetExpiryDate *gtime.Time // 达量断网过期时间
AgentShowField any // 客户展示字段设置
AgentShowValue any // 客户展示字段数据
HasTestPeriod any // 是否有测试期(0:无,1:有)
TestPeriod any // 测试期(天)
TestPeriodExpiryDate *gtime.Time // 测试期过期日期
HasSilentPeriod any // 是否有沉默期(0:无,1:有)
SilentPeriod any // 沉默期(月)
SilentPeriodExpiryDate *gtime.Time // 沉默期过期日期
HasAdditional any // 是否有附加费用(0:无 1:有)
AdditionalMultiplier any // 附加费用倍数
AdditionalAmount any // 附加金额
DataService any // 数据服务(0:关闭 1:开启)
DataTotalAmount any // 套餐流量(MB)
DataUseAmount any // 已用流量(MB)
SmsService any // 短信服务(0:关闭 1:开启)
SmsTotalAmount any // 套餐短信数量
SmsUseAmount any // 已用短信数量
VoiceService any // 语音服务(0:关闭 1:开启)
VoiceTotalAmount any // 套餐通话分钟数
VoiceUseAmount any // 已用通话分钟数
IntlRoamingService any // 国际漫游服务(0:关闭 1:开启)
IntlRoamingTotalAmount any // 套餐国际漫游流量(MB)
IntlRoamingUseAmount any // 已用国际漫游流量(MB)
Description any // 描述信息
DeptId any // 所属机构
TenantId any // 租户ID
CreatedDept any // 创建部门
CreatedBy any // 创建者
CreatedAt *gtime.Time // 创建时间
UpdatedBy any // 更新者
UpdatedAt *gtime.Time // 更新时间
}
@@ -0,0 +1,27 @@
// =================================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// =================================================================================
package do
import (
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
)
// TSimChannelTemplate is the golang structure of table t_sim_channel_template for DAO operations like Where/Data.
type TSimChannelTemplate struct {
g.Meta `orm:"table:t_sim_channel_template, do:true"`
Id any // ID
TempKey any // 模板标识
TempName any // 模板名称
TempModel any // 模板模型
TempConfig any // 模板配置
TempStatus any // 模板状态(0未启用,1启用)
TenantId any // 租户ID
CreatedDept any // 创建部门
CreatedBy any // 创建者
CreatedAt *gtime.Time // 创建时间
UpdatedBy any // 更新者
UpdatedAt *gtime.Time // 更新时间
}
@@ -0,0 +1,35 @@
// =================================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// =================================================================================
package do
import (
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
)
// TSimChannels is the golang structure of table t_sim_channels for DAO operations like Where/Data.
type TSimChannels struct {
g.Meta `orm:"table:t_sim_channels, do:true"`
Id any // ID
TempKey any // 模板标识
ChanKey any // 通道编号
ChanName any // 通道名称
ChanConfig any // 通道配置
ChanStatus any // 通道状态(0未启用,1启用)
ChanAlias any // 通道别名
RegionCode any // 通道行政区域代码(省市)
AppKey any // 应用标识
AppSecret any // 应用密钥
AccessToken any // 接口调用凭证
AccessTokenCreated *gtime.Time // 凭证申请时间
AccessTokenExpire *gtime.Time // 凭证过期时间
RefreshToken any // 刷新凭证
TenantId any // 租户ID
CreatedDept any // 创建部门
CreatedBy any // 创建者
CreatedAt *gtime.Time // 创建时间
UpdatedBy any // 更新者
UpdatedAt *gtime.Time // 更新时间
}
@@ -0,0 +1,25 @@
// =================================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// =================================================================================
package entity
import (
"github.com/gogf/gf/v2/os/gtime"
)
// TApi is the golang structure for table t_api.
type TApi struct {
Id int64 `json:"id" orm:"id" description:"ID"`
AppKey string `json:"appKey" orm:"app_key" description:"APPKey"`
AppSecret string `json:"appSecret" orm:"app_secret" description:"APPSecret"`
RateLimitPeriod int `json:"rateLimitPeriod" orm:"rate_limit_period" description:"限流周期(秒,0:不限流)"`
RateLimitCount int `json:"rateLimitCount" orm:"rate_limit_count" description:"限流次数"`
DeptId int64 `json:"deptId" orm:"dept_id" description:"所属机构"`
TenantId string `json:"tenantId" orm:"tenant_id" description:"租户ID"`
CreatedDept int64 `json:"createdDept" orm:"created_dept" description:"创建部门"`
CreatedBy int64 `json:"createdBy" orm:"created_by" description:"创建者"`
CreatedAt *gtime.Time `json:"createdAt" orm:"created_at" description:"创建时间"`
UpdatedBy int64 `json:"updatedBy" orm:"updated_by" description:"更新者"`
UpdatedAt *gtime.Time `json:"updatedAt" orm:"updated_at" description:"更新时间"`
}
@@ -0,0 +1,33 @@
// =================================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// =================================================================================
package entity
import (
"github.com/gogf/gf/v2/os/gtime"
)
// TCustomer is the golang structure for table t_customer.
type TCustomer struct {
Id int64 `json:"id" orm:"id" description:"ID"`
CKey string `json:"cKey" orm:"c_key" description:"客户编号"`
CType int `json:"cType" orm:"c_type" description:"客户类型(0:企业,1:个人)"`
CName string `json:"cName" orm:"c_name" description:"客户名称"`
CShortName string `json:"cShortName" orm:"c_short_name" description:"客户简称"`
ContactUserName string `json:"contactUserName" orm:"contact_user_name" description:"联系人"`
ContactPhone string `json:"contactPhone" orm:"contact_phone" description:"联系电话"`
LicenseNumber string `json:"licenseNumber" orm:"license_number" description:"统一社会信用代码"`
RegionCode string `json:"regionCode" orm:"region_code" description:"行政区域代码"`
Address string `json:"address" orm:"address" description:"地址"`
DepositAmount float64 `json:"depositAmount" orm:"deposit_amount" description:"预存金额"`
CreditAmountTotal float64 `json:"creditAmountTotal" orm:"credit_amount_total" description:"已授信额度"`
CreditAmountUsed float64 `json:"creditAmountUsed" orm:"credit_amount_used" description:"已使用授信额度"`
DeptId int64 `json:"deptId" orm:"dept_id" description:"所属机构"`
TenantId string `json:"tenantId" orm:"tenant_id" description:"租户ID"`
CreatedDept int64 `json:"createdDept" orm:"created_dept" description:"创建部门"`
CreatedBy int64 `json:"createdBy" orm:"created_by" description:"创建者"`
CreatedAt *gtime.Time `json:"createdAt" orm:"created_at" description:"创建时间"`
UpdatedBy int64 `json:"updatedBy" orm:"updated_by" description:"更新者"`
UpdatedAt *gtime.Time `json:"updatedAt" orm:"updated_at" description:"更新时间"`
}
@@ -0,0 +1,32 @@
// =================================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// =================================================================================
package entity
import (
"github.com/gogf/gf/v2/os/gtime"
)
// TSimAgentPackage is the golang structure for table t_sim_agent_package.
type TSimAgentPackage struct {
Id int64 `json:"id" orm:"id" description:"ID"`
PackageKey string `json:"packageKey" orm:"package_key" description:"套餐分类编码"`
PackageName string `json:"packageName" orm:"package_name" description:"套餐分类名称"`
ParentPackageKey string `json:"parentPackageKey" orm:"parent_package_key" description:"上级套餐分类编码"`
PackageStatus int `json:"packageStatus" orm:"package_status" description:"是否启用(0:未启用,1启用)"`
BillingMethod int `json:"billingMethod" orm:"billing_method" description:"计费方式(0:月,1:季,2:年)"`
ChannelPrice float64 `json:"channelPrice" orm:"channel_price" description:"渠道价格"`
MonthlyFee float64 `json:"monthlyFee" orm:"monthly_fee" description:"月租费"`
PackageDataMb float64 `json:"packageDataMb" orm:"package_data_mb" description:"套餐流量(MB)"`
PackageDataGb float64 `json:"packageDataGb" orm:"package_data_gb" description:"套餐流量(GB)"`
ValidityPeriod string `json:"validityPeriod" orm:"validity_period" description:"有效期"`
Description string `json:"description" orm:"description" description:"描述信息"`
OrderNum int `json:"orderNum" orm:"order_num" description:"排序"`
TenantId string `json:"tenantId" orm:"tenant_id" description:"租户ID"`
CreatedDept int64 `json:"createdDept" orm:"created_dept" description:"创建部门"`
CreatedBy int64 `json:"createdBy" orm:"created_by" description:"创建者"`
CreatedAt *gtime.Time `json:"createdAt" orm:"created_at" description:"创建时间"`
UpdatedBy int64 `json:"updatedBy" orm:"updated_by" description:"更新者"`
UpdatedAt *gtime.Time `json:"updatedAt" orm:"updated_at" description:"更新时间"`
}
@@ -0,0 +1,27 @@
// =================================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// =================================================================================
package entity
import (
"github.com/gogf/gf/v2/os/gtime"
)
// TSimAgentPacket is the golang structure for table t_sim_agent_packet.
type TSimAgentPacket struct {
Id int64 `json:"id" orm:"id" description:"ID"`
PackageKey string `json:"packageKey" orm:"package_key" description:"套餐分类编码"`
PacketKey string `json:"packetKey" orm:"packet_key" description:"套餐编码"`
PacketName string `json:"packetName" orm:"packet_name" description:"套餐名称"`
ValidityPeriod int `json:"validityPeriod" orm:"validity_period" description:"套餐有效期(月份)"`
PacketStatus int `json:"packetStatus" orm:"packet_status" description:"是否启用(0:未启用,1启用)"`
DiscountPercent int `json:"discountPercent" orm:"discount_percent" description:"折扣率(百分比)"`
Description string `json:"description" orm:"description" description:"描述信息"`
TenantId string `json:"tenantId" orm:"tenant_id" description:"租户ID"`
CreatedDept int64 `json:"createdDept" orm:"created_dept" description:"创建部门"`
CreatedBy int64 `json:"createdBy" orm:"created_by" description:"创建者"`
CreatedAt *gtime.Time `json:"createdAt" orm:"created_at" description:"创建时间"`
UpdatedBy int64 `json:"updatedBy" orm:"updated_by" description:"更新者"`
UpdatedAt *gtime.Time `json:"updatedAt" orm:"updated_at" description:"更新时间"`
}
@@ -0,0 +1,71 @@
// =================================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// =================================================================================
package entity
import (
"github.com/gogf/gf/v2/os/gtime"
)
// TSimCardInfo is the golang structure for table t_sim_card_info.
type TSimCardInfo struct {
Id int64 `json:"id" orm:"id" description:"ID"`
Msisdn string `json:"msisdn" orm:"msisdn" description:"SIM号码"`
Iccid string `json:"iccid" orm:"iccid" description:"SIM卡号"`
Imsi string `json:"imsi" orm:"imsi" description:"SIM识别码"`
Imei string `json:"imei" orm:"imei" description:"IMEI"`
OpenDate *gtime.Time `json:"openDate" orm:"open_date" description:"开卡日期(运营商)"`
ActivateDate *gtime.Time `json:"activateDate" orm:"activate_date" description:"激活时间(运营商)"`
AllocationBatch string `json:"allocationBatch" orm:"allocation_batch" description:"划拨批次"`
AllocationDate *gtime.Time `json:"allocationDate" orm:"allocation_date" description:"划拨日期"`
ChanKey string `json:"chanKey" orm:"chan_key" description:"通道编号"`
IsPool int `json:"isPool" orm:"is_pool" description:"是否流量池(0:否,1:是)"`
PoolKey string `json:"poolKey" orm:"pool_key" description:"流量池编号"`
BatchKey string `json:"batchKey" orm:"batch_key" description:"入库批次"`
BatchDate *gtime.Time `json:"batchDate" orm:"batch_date" description:"入库日期"`
PackageKey string `json:"packageKey" orm:"package_key" description:"套餐分类编码"`
CurrPacketKey string `json:"currPacketKey" orm:"curr_packet_key" description:"当前套餐编码"`
AllowPacketChange int `json:"allowPacketChange" orm:"allow_packet_change" description:"是否允许更改套餐(0:否 1:是)"`
CardStatus int `json:"cardStatus" orm:"card_status" description:"卡状态"`
ValidityPeriod int `json:"validityPeriod" orm:"validity_period" description:"有效期(月份)"`
ExpiryDate *gtime.Time `json:"expiryDate" orm:"expiry_date" description:"套餐到期日期"`
UpstreamExpiryDate *gtime.Time `json:"upstreamExpiryDate" orm:"upstream_expiry_date" description:"上游到期时间(运营商)"`
IpType string `json:"ipType" orm:"ip_type" description:"IP版本(人工录入)"`
NetModel string `json:"netModel" orm:"net_model" description:"网络模式(人工录入)"`
RegionCode string `json:"regionCode" orm:"region_code" description:"行政区域代码(省市)"`
HasOffNet int `json:"hasOffNet" orm:"has_off_net" description:"是否开启达量断网"`
OffNetQuota float64 `json:"offNetQuota" orm:"off_net_quota" description:"达量断网阈值(MB)"`
OffNetExpiryDate *gtime.Time `json:"offNetExpiryDate" orm:"off_net_expiry_date" description:"达量断网过期时间"`
AgentShowField string `json:"agentShowField" orm:"agent_show_field" description:"客户展示字段设置"`
AgentShowValue string `json:"agentShowValue" orm:"agent_show_value" description:"客户展示字段数据"`
HasTestPeriod int `json:"hasTestPeriod" orm:"has_test_period" description:"是否有测试期(0:无,1:有)"`
TestPeriod int `json:"testPeriod" orm:"test_period" description:"测试期(天)"`
TestPeriodExpiryDate *gtime.Time `json:"testPeriodExpiryDate" orm:"test_period_expiry_date" description:"测试期过期日期"`
HasSilentPeriod int `json:"hasSilentPeriod" orm:"has_silent_period" description:"是否有沉默期(0:无,1:有)"`
SilentPeriod int `json:"silentPeriod" orm:"silent_period" description:"沉默期(月)"`
SilentPeriodExpiryDate *gtime.Time `json:"silentPeriodExpiryDate" orm:"silent_period_expiry_date" description:"沉默期过期日期"`
HasAdditional int `json:"hasAdditional" orm:"has_additional" description:"是否有附加费用(0:无 1:有)"`
AdditionalMultiplier float64 `json:"additionalMultiplier" orm:"additional_multiplier" description:"附加费用倍数"`
AdditionalAmount float64 `json:"additionalAmount" orm:"additional_amount" description:"附加金额"`
DataService int `json:"dataService" orm:"data_service" description:"数据服务(0:关闭 1:开启)"`
DataTotalAmount float64 `json:"dataTotalAmount" orm:"data_total_amount" description:"套餐流量(MB)"`
DataUseAmount float64 `json:"dataUseAmount" orm:"data_use_amount" description:"已用流量(MB)"`
SmsService int `json:"smsService" orm:"sms_service" description:"短信服务(0:关闭 1:开启)"`
SmsTotalAmount float64 `json:"smsTotalAmount" orm:"sms_total_amount" description:"套餐短信数量"`
SmsUseAmount float64 `json:"smsUseAmount" orm:"sms_use_amount" description:"已用短信数量"`
VoiceService int `json:"voiceService" orm:"voice_service" description:"语音服务(0:关闭 1:开启)"`
VoiceTotalAmount float64 `json:"voiceTotalAmount" orm:"voice_total_amount" description:"套餐通话分钟数"`
VoiceUseAmount float64 `json:"voiceUseAmount" orm:"voice_use_amount" description:"已用通话分钟数"`
IntlRoamingService int `json:"intlRoamingService" orm:"intl_roaming_service" description:"国际漫游服务(0:关闭 1:开启)"`
IntlRoamingTotalAmount float64 `json:"intlRoamingTotalAmount" orm:"intl_roaming_total_amount" description:"套餐国际漫游流量(MB)"`
IntlRoamingUseAmount float64 `json:"intlRoamingUseAmount" orm:"intl_roaming_use_amount" description:"已用国际漫游流量(MB)"`
Description string `json:"description" orm:"description" description:"描述信息"`
DeptId int64 `json:"deptId" orm:"dept_id" description:"所属机构"`
TenantId string `json:"tenantId" orm:"tenant_id" description:"租户ID"`
CreatedDept int64 `json:"createdDept" orm:"created_dept" description:"创建部门"`
CreatedBy int64 `json:"createdBy" orm:"created_by" description:"创建者"`
CreatedAt *gtime.Time `json:"createdAt" orm:"created_at" description:"创建时间"`
UpdatedBy int64 `json:"updatedBy" orm:"updated_by" description:"更新者"`
UpdatedAt *gtime.Time `json:"updatedAt" orm:"updated_at" description:"更新时间"`
}
@@ -0,0 +1,25 @@
// =================================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// =================================================================================
package entity
import (
"github.com/gogf/gf/v2/os/gtime"
)
// TSimChannelTemplate is the golang structure for table t_sim_channel_template.
type TSimChannelTemplate struct {
Id int64 `json:"id" orm:"id" description:"ID"`
TempKey string `json:"tempKey" orm:"temp_key" description:"模板标识"`
TempName string `json:"tempName" orm:"temp_name" description:"模板名称"`
TempModel string `json:"tempModel" orm:"temp_model" description:"模板模型"`
TempConfig string `json:"tempConfig" orm:"temp_config" description:"模板配置"`
TempStatus int `json:"tempStatus" orm:"temp_status" description:"模板状态(0未启用,1启用)"`
TenantId string `json:"tenantId" orm:"tenant_id" description:"租户ID"`
CreatedDept int64 `json:"createdDept" orm:"created_dept" description:"创建部门"`
CreatedBy int64 `json:"createdBy" orm:"created_by" description:"创建者"`
CreatedAt *gtime.Time `json:"createdAt" orm:"created_at" description:"创建时间"`
UpdatedBy int64 `json:"updatedBy" orm:"updated_by" description:"更新者"`
UpdatedAt *gtime.Time `json:"updatedAt" orm:"updated_at" description:"更新时间"`
}
@@ -0,0 +1,33 @@
// =================================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// =================================================================================
package entity
import (
"github.com/gogf/gf/v2/os/gtime"
)
// TSimChannels is the golang structure for table t_sim_channels.
type TSimChannels struct {
Id int64 `json:"id" orm:"id" description:"ID"`
TempKey string `json:"tempKey" orm:"temp_key" description:"模板标识"`
ChanKey string `json:"chanKey" orm:"chan_key" description:"通道编号"`
ChanName string `json:"chanName" orm:"chan_name" description:"通道名称"`
ChanConfig string `json:"chanConfig" orm:"chan_config" description:"通道配置"`
ChanStatus int `json:"chanStatus" orm:"chan_status" description:"通道状态(0未启用,1启用)"`
ChanAlias string `json:"chanAlias" orm:"chan_alias" description:"通道别名"`
RegionCode string `json:"regionCode" orm:"region_code" description:"通道行政区域代码(省市)"`
AppKey string `json:"appKey" orm:"app_key" description:"应用标识"`
AppSecret string `json:"appSecret" orm:"app_secret" description:"应用密钥"`
AccessToken string `json:"accessToken" orm:"access_token" description:"接口调用凭证"`
AccessTokenCreated *gtime.Time `json:"accessTokenCreated" orm:"access_token_created" description:"凭证申请时间"`
AccessTokenExpire *gtime.Time `json:"accessTokenExpire" orm:"access_token_expire" description:"凭证过期时间"`
RefreshToken string `json:"refreshToken" orm:"refresh_token" description:"刷新凭证"`
TenantId string `json:"tenantId" orm:"tenant_id" description:"租户ID"`
CreatedDept int64 `json:"createdDept" orm:"created_dept" description:"创建部门"`
CreatedBy int64 `json:"createdBy" orm:"created_by" description:"创建者"`
CreatedAt *gtime.Time `json:"createdAt" orm:"created_at" description:"创建时间"`
UpdatedBy int64 `json:"updatedBy" orm:"updated_by" description:"更新者"`
UpdatedAt *gtime.Time `json:"updatedAt" orm:"updated_at" description:"更新时间"`
}
@@ -0,0 +1,43 @@
package model
import (
"context"
"git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/model/entity"
"github.com/gogf/gf/v2/os/gtime"
)
type SimChannelConfigItemCache struct {
Template *entity.TSimChannelTemplate
Channel *entity.TSimChannels
IsNormal *int // global_yes_or_no: 0=yes, 1=no; NULL is not eligible.
Version string
}
// CanCreateInstance determines whether a channel may have a runtime instance.
func (c *SimChannelConfigItemCache) CanCreateInstance() bool {
return c != nil && c.Channel != nil && c.IsNormal != nil && *c.IsNormal == 0
}
type SimCardTempI interface {
NewTemp(ctx context.Context, channelConfig *SimChannelConfigItemCache) SimCardChannelI
}
// 接口定义
type SimCardChannelI interface {
SimBase(ctx context.Context, input *SimBaseReqJson) (SimBaseResJson, error)
}
type SimCardChannelCache struct {
Instance SimCardChannelI
Version string
}
type UpdateAccessTokenInput struct {
ChanKey string
AccessToken string
AccessTokenCreated *gtime.Time
AccessTokenExpire *gtime.Time
RefreshToken string
RefreshTokenExpire *gtime.Time
}
@@ -0,0 +1,36 @@
package model
import "encoding/json"
// 物联网卡标识
type SimKey struct {
ChanKey string `json:"chanKey" dc:"通道标识"`
ICCID string `json:"iccid" dc:"联网卡集成电路卡识别码20位"`
IMSI string `json:"imsi" dc:"国际移动用户识别码"`
MSISDN string `json:"msisdn" dc:"移动用户识别码"`
}
// 物联网卡基础请求参数
type SimBaseReq[T any] struct {
SimKeys []*SimKey `json:"simKeys" dc:"SIM卡标识列表"`
ReqData T `json:"reqData" dc:"请求数据"`
}
// 响应数据参数
type SimResponseData[T any] struct {
Code int `json:"code" dc:"状态码"`
Message string `json:"message" dc:"消息"`
Data T `json:"data" dc:"数据"`
}
// 物联网卡基础响应参数
type SimBaseRes[T any] struct {
Code int `json:"code" dc:"状态码"`
Message string `json:"message" dc:"消息"`
ErrCode string `json:"errCode" dc:"运营商原始错误码"`
SimKey *SimKey `json:"simKey" dc:"SIM卡标识"`
ResData T `json:"resData" dc:"响应数据"`
}
type SimBaseReqJson = SimBaseReq[json.RawMessage]
type SimBaseResJson = []*SimBaseRes[json.RawMessage]
+1
View File
@@ -0,0 +1 @@
package packed
+206
View File
@@ -0,0 +1,206 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.11
// protoc v5.29.3
// source: common.proto
package pb
import (
reflect "reflect"
sync "sync"
unsafe "unsafe"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type HeadReq struct {
state protoimpl.MessageState `protogen:"open.v1"`
RouterId int64 `protobuf:"varint,1,opt,name=router_id,json=routerId,proto3" json:"router_id,omitempty" dc:"路由id json:'router_id'"` // 路由id json:"router_id"
TraceId string `protobuf:"bytes,2,opt,name=trace_id,json=traceId,proto3" json:"trace_id,omitempty" dc:"追踪id json:'trace_id'"` // 追踪id json:"trace_id"
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *HeadReq) Reset() {
*x = HeadReq{}
mi := &file_common_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *HeadReq) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*HeadReq) ProtoMessage() {}
func (x *HeadReq) ProtoReflect() protoreflect.Message {
mi := &file_common_proto_msgTypes[0]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use HeadReq.ProtoReflect.Descriptor instead.
func (*HeadReq) Descriptor() ([]byte, []int) {
return file_common_proto_rawDescGZIP(), []int{0}
}
func (x *HeadReq) GetRouterId() int64 {
if x != nil {
return x.RouterId
}
return 0
}
func (x *HeadReq) GetTraceId() string {
if x != nil {
return x.TraceId
}
return ""
}
type HeadRes struct {
state protoimpl.MessageState `protogen:"open.v1"`
Code int32 `protobuf:"varint,1,opt,name=code,proto3" json:"code,omitempty" dc:"错误码 json:'code'"` // 错误码 json:"code"
Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty" dc:"错误信息 json:'message'"` // 错误信息 json:"message"
RouterId int64 `protobuf:"varint,3,opt,name=router_id,json=routerId,proto3" json:"router_id,omitempty" dc:"路由id json:'router_id'"` // 路由id json:"router_id"
TraceId string `protobuf:"bytes,4,opt,name=trace_id,json=traceId,proto3" json:"trace_id,omitempty" dc:"追踪id json:'trace_id'"` // 追踪id json:"trace_id"
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *HeadRes) Reset() {
*x = HeadRes{}
mi := &file_common_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *HeadRes) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*HeadRes) ProtoMessage() {}
func (x *HeadRes) ProtoReflect() protoreflect.Message {
mi := &file_common_proto_msgTypes[1]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use HeadRes.ProtoReflect.Descriptor instead.
func (*HeadRes) Descriptor() ([]byte, []int) {
return file_common_proto_rawDescGZIP(), []int{1}
}
func (x *HeadRes) GetCode() int32 {
if x != nil {
return x.Code
}
return 0
}
func (x *HeadRes) GetMessage() string {
if x != nil {
return x.Message
}
return ""
}
func (x *HeadRes) GetRouterId() int64 {
if x != nil {
return x.RouterId
}
return 0
}
func (x *HeadRes) GetTraceId() string {
if x != nil {
return x.TraceId
}
return ""
}
var File_common_proto protoreflect.FileDescriptor
const file_common_proto_rawDesc = "" +
"\n" +
"\fcommon.proto\x12\bPbCommon\"A\n" +
"\aHeadReq\x12\x1b\n" +
"\trouter_id\x18\x01 \x01(\x03R\brouterId\x12\x19\n" +
"\btrace_id\x18\x02 \x01(\tR\atraceId\"o\n" +
"\aHeadRes\x12\x12\n" +
"\x04code\x18\x01 \x01(\x05R\x04code\x12\x18\n" +
"\amessage\x18\x02 \x01(\tR\amessage\x12\x1b\n" +
"\trouter_id\x18\x03 \x01(\x03R\brouterId\x12\x19\n" +
"\btrace_id\x18\x04 \x01(\tR\atraceIdB\x05Z\x03pb/b\x06proto3"
var (
file_common_proto_rawDescOnce sync.Once
file_common_proto_rawDescData []byte
)
func file_common_proto_rawDescGZIP() []byte {
file_common_proto_rawDescOnce.Do(func() {
file_common_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_common_proto_rawDesc), len(file_common_proto_rawDesc)))
})
return file_common_proto_rawDescData
}
var file_common_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_common_proto_goTypes = []any{
(*HeadReq)(nil), // 0: PbCommon.HeadReq
(*HeadRes)(nil), // 1: PbCommon.HeadRes
}
var file_common_proto_depIdxs = []int32{
0, // [0:0] is the sub-list for method output_type
0, // [0:0] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_common_proto_init() }
func file_common_proto_init() {
if File_common_proto != nil {
return
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_common_proto_rawDesc), len(file_common_proto_rawDesc)),
NumEnums: 0,
NumMessages: 2,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_common_proto_goTypes,
DependencyIndexes: file_common_proto_depIdxs,
MessageInfos: file_common_proto_msgTypes,
}.Build()
File_common_proto = out.File
file_common_proto_goTypes = nil
file_common_proto_depIdxs = nil
}
+257
View File
@@ -0,0 +1,257 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.11
// protoc v5.29.3
// source: info.proto
package pb
import (
reflect "reflect"
sync "sync"
unsafe "unsafe"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type InfoReq struct {
state protoimpl.MessageState `protogen:"open.v1"`
Head *HeadReq `protobuf:"bytes,1,opt,name=head,proto3" json:"head,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *InfoReq) Reset() {
*x = InfoReq{}
mi := &file_info_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *InfoReq) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*InfoReq) ProtoMessage() {}
func (x *InfoReq) ProtoReflect() protoreflect.Message {
mi := &file_info_proto_msgTypes[0]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use InfoReq.ProtoReflect.Descriptor instead.
func (*InfoReq) Descriptor() ([]byte, []int) {
return file_info_proto_rawDescGZIP(), []int{0}
}
func (x *InfoReq) GetHead() *HeadReq {
if x != nil {
return x.Head
}
return nil
}
type InfoRes struct {
state protoimpl.MessageState `protogen:"open.v1"`
Head *HeadRes `protobuf:"bytes,1,opt,name=head,proto3" json:"head,omitempty"`
Type string `protobuf:"bytes,2,opt,name=type,proto3" json:"type,omitempty"`
Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"`
Title string `protobuf:"bytes,4,opt,name=title,proto3" json:"title,omitempty"`
Description string `protobuf:"bytes,5,opt,name=description,proto3" json:"description,omitempty"`
Version string `protobuf:"bytes,6,opt,name=version,proto3" json:"version,omitempty"`
Build string `protobuf:"bytes,7,opt,name=build,proto3" json:"build,omitempty"`
Commit string `protobuf:"bytes,8,opt,name=commit,proto3" json:"commit,omitempty"`
Data map[string]string `protobuf:"bytes,9,rep,name=data,proto3" json:"data,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *InfoRes) Reset() {
*x = InfoRes{}
mi := &file_info_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *InfoRes) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*InfoRes) ProtoMessage() {}
func (x *InfoRes) ProtoReflect() protoreflect.Message {
mi := &file_info_proto_msgTypes[1]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use InfoRes.ProtoReflect.Descriptor instead.
func (*InfoRes) Descriptor() ([]byte, []int) {
return file_info_proto_rawDescGZIP(), []int{1}
}
func (x *InfoRes) GetHead() *HeadRes {
if x != nil {
return x.Head
}
return nil
}
func (x *InfoRes) GetType() string {
if x != nil {
return x.Type
}
return ""
}
func (x *InfoRes) GetName() string {
if x != nil {
return x.Name
}
return ""
}
func (x *InfoRes) GetTitle() string {
if x != nil {
return x.Title
}
return ""
}
func (x *InfoRes) GetDescription() string {
if x != nil {
return x.Description
}
return ""
}
func (x *InfoRes) GetVersion() string {
if x != nil {
return x.Version
}
return ""
}
func (x *InfoRes) GetBuild() string {
if x != nil {
return x.Build
}
return ""
}
func (x *InfoRes) GetCommit() string {
if x != nil {
return x.Commit
}
return ""
}
func (x *InfoRes) GetData() map[string]string {
if x != nil {
return x.Data
}
return nil
}
var File_info_proto protoreflect.FileDescriptor
const file_info_proto_rawDesc = "" +
"\n" +
"\n" +
"info.proto\x12\x06PbInfo\x1a\fcommon.proto\"0\n" +
"\aInfoReq\x12%\n" +
"\x04head\x18\x01 \x01(\v2\x11.PbCommon.HeadReqR\x04head\"\xc0\x02\n" +
"\aInfoRes\x12%\n" +
"\x04head\x18\x01 \x01(\v2\x11.PbCommon.HeadResR\x04head\x12\x12\n" +
"\x04type\x18\x02 \x01(\tR\x04type\x12\x12\n" +
"\x04name\x18\x03 \x01(\tR\x04name\x12\x14\n" +
"\x05title\x18\x04 \x01(\tR\x05title\x12 \n" +
"\vdescription\x18\x05 \x01(\tR\vdescription\x12\x18\n" +
"\aversion\x18\x06 \x01(\tR\aversion\x12\x14\n" +
"\x05build\x18\a \x01(\tR\x05build\x12\x16\n" +
"\x06commit\x18\b \x01(\tR\x06commit\x12-\n" +
"\x04data\x18\t \x03(\v2\x19.PbInfo.InfoRes.DataEntryR\x04data\x1a7\n" +
"\tDataEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x0122\n" +
"\x04Info\x12*\n" +
"\x04Info\x12\x0f.PbInfo.InfoReq\x1a\x0f.PbInfo.InfoRes\"\x00B\x05Z\x03pb/b\x06proto3"
var (
file_info_proto_rawDescOnce sync.Once
file_info_proto_rawDescData []byte
)
func file_info_proto_rawDescGZIP() []byte {
file_info_proto_rawDescOnce.Do(func() {
file_info_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_info_proto_rawDesc), len(file_info_proto_rawDesc)))
})
return file_info_proto_rawDescData
}
var file_info_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
var file_info_proto_goTypes = []any{
(*InfoReq)(nil), // 0: PbInfo.InfoReq
(*InfoRes)(nil), // 1: PbInfo.InfoRes
nil, // 2: PbInfo.InfoRes.DataEntry
(*HeadReq)(nil), // 3: PbCommon.HeadReq
(*HeadRes)(nil), // 4: PbCommon.HeadRes
}
var file_info_proto_depIdxs = []int32{
3, // 0: PbInfo.InfoReq.head:type_name -> PbCommon.HeadReq
4, // 1: PbInfo.InfoRes.head:type_name -> PbCommon.HeadRes
2, // 2: PbInfo.InfoRes.data:type_name -> PbInfo.InfoRes.DataEntry
0, // 3: PbInfo.Info.Info:input_type -> PbInfo.InfoReq
1, // 4: PbInfo.Info.Info:output_type -> PbInfo.InfoRes
4, // [4:5] is the sub-list for method output_type
3, // [3:4] is the sub-list for method input_type
3, // [3:3] is the sub-list for extension type_name
3, // [3:3] is the sub-list for extension extendee
0, // [0:3] is the sub-list for field type_name
}
func init() { file_info_proto_init() }
func file_info_proto_init() {
if File_info_proto != nil {
return
}
file_common_proto_init()
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_info_proto_rawDesc), len(file_info_proto_rawDesc)),
NumEnums: 0,
NumMessages: 3,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_info_proto_goTypes,
DependencyIndexes: file_info_proto_depIdxs,
MessageInfos: file_info_proto_msgTypes,
}.Build()
File_info_proto = out.File
file_info_proto_goTypes = nil
file_info_proto_depIdxs = nil
}
+122
View File
@@ -0,0 +1,122 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.6.1
// - protoc v5.29.3
// source: info.proto
package pb
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.64.0 or later.
const _ = grpc.SupportPackageIsVersion9
const (
Info_Info_FullMethodName = "/PbInfo.Info/Info"
)
// InfoClient is the client API for Info service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type InfoClient interface {
Info(ctx context.Context, in *InfoReq, opts ...grpc.CallOption) (*InfoRes, error)
}
type infoClient struct {
cc grpc.ClientConnInterface
}
func NewInfoClient(cc grpc.ClientConnInterface) InfoClient {
return &infoClient{cc}
}
func (c *infoClient) Info(ctx context.Context, in *InfoReq, opts ...grpc.CallOption) (*InfoRes, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(InfoRes)
err := c.cc.Invoke(ctx, Info_Info_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
// InfoServer is the server API for Info service.
// All implementations must embed UnimplementedInfoServer
// for forward compatibility.
type InfoServer interface {
Info(context.Context, *InfoReq) (*InfoRes, error)
mustEmbedUnimplementedInfoServer()
}
// UnimplementedInfoServer must be embedded to have
// forward compatible implementations.
//
// NOTE: this should be embedded by value instead of pointer to avoid a nil
// pointer dereference when methods are called.
type UnimplementedInfoServer struct{}
func (UnimplementedInfoServer) Info(context.Context, *InfoReq) (*InfoRes, error) {
return nil, status.Error(codes.Unimplemented, "method Info not implemented")
}
func (UnimplementedInfoServer) mustEmbedUnimplementedInfoServer() {}
func (UnimplementedInfoServer) testEmbeddedByValue() {}
// UnsafeInfoServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to InfoServer will
// result in compilation errors.
type UnsafeInfoServer interface {
mustEmbedUnimplementedInfoServer()
}
func RegisterInfoServer(s grpc.ServiceRegistrar, srv InfoServer) {
// If the following call panics, it indicates UnimplementedInfoServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
t.testEmbeddedByValue()
}
s.RegisterService(&Info_ServiceDesc, srv)
}
func _Info_Info_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(InfoReq)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(InfoServer).Info(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Info_Info_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(InfoServer).Info(ctx, req.(*InfoReq))
}
return interceptor(ctx, in, info, handler)
}
// Info_ServiceDesc is the grpc.ServiceDesc for Info service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var Info_ServiceDesc = grpc.ServiceDesc{
ServiceName: "PbInfo.Info",
HandlerType: (*InfoServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "Info",
Handler: _Info_Info_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "info.proto",
}
+632
View File
@@ -0,0 +1,632 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.11
// protoc v5.29.3
// source: msgbus.proto
package pb
import (
reflect "reflect"
sync "sync"
unsafe "unsafe"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type EnumMsgType int32
const (
EnumMsgType_MsgTypeNone EnumMsgType = 0 // 未知消息类型
EnumMsgType_MsgTypeLogin EnumMsgType = 1 // 客户端长连接登录包
EnumMsgType_MsgTypeLogout EnumMsgType = 2 // 客户端长连接登出包
EnumMsgType_MsgTypeConnect EnumMsgType = 3 // 新建链接消息
EnumMsgType_MsgTypeDisconnect EnumMsgType = 4 // 关闭链接消息
EnumMsgType_MsgTypeData EnumMsgType = 5 // 消息透传转发
)
// Enum value maps for EnumMsgType.
var (
EnumMsgType_name = map[int32]string{
0: "MsgTypeNone",
1: "MsgTypeLogin",
2: "MsgTypeLogout",
3: "MsgTypeConnect",
4: "MsgTypeDisconnect",
5: "MsgTypeData",
}
EnumMsgType_value = map[string]int32{
"MsgTypeNone": 0,
"MsgTypeLogin": 1,
"MsgTypeLogout": 2,
"MsgTypeConnect": 3,
"MsgTypeDisconnect": 4,
"MsgTypeData": 5,
}
)
func (x EnumMsgType) Enum() *EnumMsgType {
p := new(EnumMsgType)
*p = x
return p
}
func (x EnumMsgType) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (EnumMsgType) Descriptor() protoreflect.EnumDescriptor {
return file_msgbus_proto_enumTypes[0].Descriptor()
}
func (EnumMsgType) Type() protoreflect.EnumType {
return &file_msgbus_proto_enumTypes[0]
}
func (x EnumMsgType) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use EnumMsgType.Descriptor instead.
func (EnumMsgType) EnumDescriptor() ([]byte, []int) {
return file_msgbus_proto_rawDescGZIP(), []int{0}
}
// 渠道类型
type EnumChannelType int32
const (
EnumChannelType_ChannelTypeNone EnumChannelType = 0
EnumChannelType_ChannelTypeMqtt EnumChannelType = 1 // 1-MQTT服务器
EnumChannelType_ChannelTypeTcp EnumChannelType = 2 // 2-TCP服务器
EnumChannelType_ChannelTypeUdp EnumChannelType = 3 // 3-UDP服务器
EnumChannelType_ChannelTypeHttp EnumChannelType = 4 // 4-HTTP服务
EnumChannelType_ChannelTypeWs EnumChannelType = 5 // 5-WebSocket服务
EnumChannelType_ChannelTypeDb EnumChannelType = 6 // 6-数据库服务
EnumChannelType_ChannelTypeInner EnumChannelType = 100 // 100-内网流转通道
)
// Enum value maps for EnumChannelType.
var (
EnumChannelType_name = map[int32]string{
0: "ChannelTypeNone",
1: "ChannelTypeMqtt",
2: "ChannelTypeTcp",
3: "ChannelTypeUdp",
4: "ChannelTypeHttp",
5: "ChannelTypeWs",
6: "ChannelTypeDb",
100: "ChannelTypeInner",
}
EnumChannelType_value = map[string]int32{
"ChannelTypeNone": 0,
"ChannelTypeMqtt": 1,
"ChannelTypeTcp": 2,
"ChannelTypeUdp": 3,
"ChannelTypeHttp": 4,
"ChannelTypeWs": 5,
"ChannelTypeDb": 6,
"ChannelTypeInner": 100,
}
)
func (x EnumChannelType) Enum() *EnumChannelType {
p := new(EnumChannelType)
*p = x
return p
}
func (x EnumChannelType) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (EnumChannelType) Descriptor() protoreflect.EnumDescriptor {
return file_msgbus_proto_enumTypes[1].Descriptor()
}
func (EnumChannelType) Type() protoreflect.EnumType {
return &file_msgbus_proto_enumTypes[1]
}
func (x EnumChannelType) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use EnumChannelType.Descriptor instead.
func (EnumChannelType) EnumDescriptor() ([]byte, []int) {
return file_msgbus_proto_rawDescGZIP(), []int{1}
}
// 渠道信息
type Channel struct {
state protoimpl.MessageState `protogen:"open.v1"`
ChannelId int64 `protobuf:"varint,1,opt,name=channel_id,json=channelId,proto3" json:"channel_id,omitempty" dc:"渠道ID"` // 渠道ID
ChannelType int32 `protobuf:"varint,2,opt,name=channel_type,json=channelType,proto3" json:"channel_type,omitempty" dc:"渠道类型 EnumChannelType"` // 渠道类型 EnumChannelType
ServerId int64 `protobuf:"varint,3,opt,name=server_id,json=serverId,proto3" json:"server_id,omitempty" dc:"服务ID"` // 服务ID
CreateTime int64 `protobuf:"varint,4,opt,name=create_time,json=createTime,proto3" json:"create_time,omitempty" dc:"启动时间戳(秒)"` // 启动时间戳(秒)
TenantId string `protobuf:"bytes,5,opt,name=tenant_id,json=tenantId,proto3" json:"tenant_id,omitempty" dc:"租户ID"` // 租户ID
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *Channel) Reset() {
*x = Channel{}
mi := &file_msgbus_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *Channel) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Channel) ProtoMessage() {}
func (x *Channel) ProtoReflect() protoreflect.Message {
mi := &file_msgbus_proto_msgTypes[0]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Channel.ProtoReflect.Descriptor instead.
func (*Channel) Descriptor() ([]byte, []int) {
return file_msgbus_proto_rawDescGZIP(), []int{0}
}
func (x *Channel) GetChannelId() int64 {
if x != nil {
return x.ChannelId
}
return 0
}
func (x *Channel) GetChannelType() int32 {
if x != nil {
return x.ChannelType
}
return 0
}
func (x *Channel) GetServerId() int64 {
if x != nil {
return x.ServerId
}
return 0
}
func (x *Channel) GetCreateTime() int64 {
if x != nil {
return x.CreateTime
}
return 0
}
func (x *Channel) GetTenantId() string {
if x != nil {
return x.TenantId
}
return ""
}
// 链接信息
type Conn struct {
state protoimpl.MessageState `protogen:"open.v1"`
ConnId int64 `protobuf:"varint,1,opt,name=conn_id,json=connId,proto3" json:"conn_id,omitempty" dc:"链接ID"` // 链接ID
CreateTime int64 `protobuf:"varint,2,opt,name=create_time,json=createTime,proto3" json:"create_time,omitempty" dc:"链接时间戳(秒)"` // 链接时间戳(秒)
LastRecvTime int64 `protobuf:"varint,3,opt,name=last_recv_time,json=lastRecvTime,proto3" json:"last_recv_time,omitempty" dc:"最后接收时间戳(秒)"` // 最后接收时间戳(秒)
LastSendTime int64 `protobuf:"varint,4,opt,name=last_send_time,json=lastSendTime,proto3" json:"last_send_time,omitempty" dc:"最后发送时间戳(秒)"` // 最后发送时间戳(秒)
ClientId string `protobuf:"bytes,5,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty" dc:"客户端ID"` // 客户端ID
UserName string `protobuf:"bytes,6,opt,name=user_name,json=userName,proto3" json:"user_name,omitempty" dc:"客户端登录名称"` // 客户端登录名称
UserPass string `protobuf:"bytes,7,opt,name=user_pass,json=userPass,proto3" json:"user_pass,omitempty" dc:"客户端登录密码"` // 客户端登录密码
ProductKey string `protobuf:"bytes,8,opt,name=product_key,json=productKey,proto3" json:"product_key,omitempty" dc:"绑定主产品KEY"` // 绑定主产品KEY
DeviceKey string `protobuf:"bytes,9,opt,name=device_key,json=deviceKey,proto3" json:"device_key,omitempty" dc:"绑定主设备KEY"` // 绑定主设备KEY
RemoteAddr string `protobuf:"bytes,10,opt,name=remote_addr,json=remoteAddr,proto3" json:"remote_addr,omitempty"`
LocalAddr string `protobuf:"bytes,11,opt,name=local_addr,json=localAddr,proto3" json:"local_addr,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *Conn) Reset() {
*x = Conn{}
mi := &file_msgbus_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *Conn) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Conn) ProtoMessage() {}
func (x *Conn) ProtoReflect() protoreflect.Message {
mi := &file_msgbus_proto_msgTypes[1]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Conn.ProtoReflect.Descriptor instead.
func (*Conn) Descriptor() ([]byte, []int) {
return file_msgbus_proto_rawDescGZIP(), []int{1}
}
func (x *Conn) GetConnId() int64 {
if x != nil {
return x.ConnId
}
return 0
}
func (x *Conn) GetCreateTime() int64 {
if x != nil {
return x.CreateTime
}
return 0
}
func (x *Conn) GetLastRecvTime() int64 {
if x != nil {
return x.LastRecvTime
}
return 0
}
func (x *Conn) GetLastSendTime() int64 {
if x != nil {
return x.LastSendTime
}
return 0
}
func (x *Conn) GetClientId() string {
if x != nil {
return x.ClientId
}
return ""
}
func (x *Conn) GetUserName() string {
if x != nil {
return x.UserName
}
return ""
}
func (x *Conn) GetUserPass() string {
if x != nil {
return x.UserPass
}
return ""
}
func (x *Conn) GetProductKey() string {
if x != nil {
return x.ProductKey
}
return ""
}
func (x *Conn) GetDeviceKey() string {
if x != nil {
return x.DeviceKey
}
return ""
}
func (x *Conn) GetRemoteAddr() string {
if x != nil {
return x.RemoteAddr
}
return ""
}
func (x *Conn) GetLocalAddr() string {
if x != nil {
return x.LocalAddr
}
return ""
}
type SendMsgReq struct {
state protoimpl.MessageState `protogen:"open.v1"`
Head *HeadReq `protobuf:"bytes,1,opt,name=head,proto3" json:"head,omitempty"`
Channel *Channel `protobuf:"bytes,2,opt,name=channel,proto3" json:"channel,omitempty" dc:"渠道信息"` // 渠道信息
Conn *Conn `protobuf:"bytes,3,opt,name=conn,proto3" json:"conn,omitempty" dc:"链接信息"` // 链接信息
MsgType int32 `protobuf:"varint,4,opt,name=msg_type,json=msgType,proto3" json:"msg_type,omitempty" dc:"消息类型"` // 消息类型
Topic string `protobuf:"bytes,5,opt,name=topic,proto3" json:"topic,omitempty"`
Data []byte `protobuf:"bytes,6,opt,name=data,proto3" json:"data,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *SendMsgReq) Reset() {
*x = SendMsgReq{}
mi := &file_msgbus_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *SendMsgReq) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SendMsgReq) ProtoMessage() {}
func (x *SendMsgReq) ProtoReflect() protoreflect.Message {
mi := &file_msgbus_proto_msgTypes[2]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SendMsgReq.ProtoReflect.Descriptor instead.
func (*SendMsgReq) Descriptor() ([]byte, []int) {
return file_msgbus_proto_rawDescGZIP(), []int{2}
}
func (x *SendMsgReq) GetHead() *HeadReq {
if x != nil {
return x.Head
}
return nil
}
func (x *SendMsgReq) GetChannel() *Channel {
if x != nil {
return x.Channel
}
return nil
}
func (x *SendMsgReq) GetConn() *Conn {
if x != nil {
return x.Conn
}
return nil
}
func (x *SendMsgReq) GetMsgType() int32 {
if x != nil {
return x.MsgType
}
return 0
}
func (x *SendMsgReq) GetTopic() string {
if x != nil {
return x.Topic
}
return ""
}
func (x *SendMsgReq) GetData() []byte {
if x != nil {
return x.Data
}
return nil
}
type SendMsgRes struct {
state protoimpl.MessageState `protogen:"open.v1"`
Head *HeadRes `protobuf:"bytes,1,opt,name=head,proto3" json:"head,omitempty"`
Conn *Conn `protobuf:"bytes,2,opt,name=conn,proto3" json:"conn,omitempty"`
Topic string `protobuf:"bytes,3,opt,name=topic,proto3" json:"topic,omitempty"`
Data []byte `protobuf:"bytes,4,opt,name=data,proto3" json:"data,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *SendMsgRes) Reset() {
*x = SendMsgRes{}
mi := &file_msgbus_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *SendMsgRes) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SendMsgRes) ProtoMessage() {}
func (x *SendMsgRes) ProtoReflect() protoreflect.Message {
mi := &file_msgbus_proto_msgTypes[3]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SendMsgRes.ProtoReflect.Descriptor instead.
func (*SendMsgRes) Descriptor() ([]byte, []int) {
return file_msgbus_proto_rawDescGZIP(), []int{3}
}
func (x *SendMsgRes) GetHead() *HeadRes {
if x != nil {
return x.Head
}
return nil
}
func (x *SendMsgRes) GetConn() *Conn {
if x != nil {
return x.Conn
}
return nil
}
func (x *SendMsgRes) GetTopic() string {
if x != nil {
return x.Topic
}
return ""
}
func (x *SendMsgRes) GetData() []byte {
if x != nil {
return x.Data
}
return nil
}
var File_msgbus_proto protoreflect.FileDescriptor
const file_msgbus_proto_rawDesc = "" +
"\n" +
"\fmsgbus.proto\x12\bPbMsgBus\x1a\fcommon.proto\"\xa6\x01\n" +
"\aChannel\x12\x1d\n" +
"\n" +
"channel_id\x18\x01 \x01(\x03R\tchannelId\x12!\n" +
"\fchannel_type\x18\x02 \x01(\x05R\vchannelType\x12\x1b\n" +
"\tserver_id\x18\x03 \x01(\x03R\bserverId\x12\x1f\n" +
"\vcreate_time\x18\x04 \x01(\x03R\n" +
"createTime\x12\x1b\n" +
"\ttenant_id\x18\x05 \x01(\tR\btenantId\"\xe3\x02\n" +
"\x04Conn\x12\x17\n" +
"\aconn_id\x18\x01 \x01(\x03R\x06connId\x12\x1f\n" +
"\vcreate_time\x18\x02 \x01(\x03R\n" +
"createTime\x12$\n" +
"\x0elast_recv_time\x18\x03 \x01(\x03R\flastRecvTime\x12$\n" +
"\x0elast_send_time\x18\x04 \x01(\x03R\flastSendTime\x12\x1b\n" +
"\tclient_id\x18\x05 \x01(\tR\bclientId\x12\x1b\n" +
"\tuser_name\x18\x06 \x01(\tR\buserName\x12\x1b\n" +
"\tuser_pass\x18\a \x01(\tR\buserPass\x12\x1f\n" +
"\vproduct_key\x18\b \x01(\tR\n" +
"productKey\x12\x1d\n" +
"\n" +
"device_key\x18\t \x01(\tR\tdeviceKey\x12\x1f\n" +
"\vremote_addr\x18\n" +
" \x01(\tR\n" +
"remoteAddr\x12\x1d\n" +
"\n" +
"local_addr\x18\v \x01(\tR\tlocalAddr\"\xc9\x01\n" +
"\n" +
"SendMsgReq\x12%\n" +
"\x04head\x18\x01 \x01(\v2\x11.PbCommon.HeadReqR\x04head\x12+\n" +
"\achannel\x18\x02 \x01(\v2\x11.PbMsgBus.ChannelR\achannel\x12\"\n" +
"\x04conn\x18\x03 \x01(\v2\x0e.PbMsgBus.ConnR\x04conn\x12\x19\n" +
"\bmsg_type\x18\x04 \x01(\x05R\amsgType\x12\x14\n" +
"\x05topic\x18\x05 \x01(\tR\x05topic\x12\x12\n" +
"\x04data\x18\x06 \x01(\fR\x04data\"\x81\x01\n" +
"\n" +
"SendMsgRes\x12%\n" +
"\x04head\x18\x01 \x01(\v2\x11.PbCommon.HeadResR\x04head\x12\"\n" +
"\x04conn\x18\x02 \x01(\v2\x0e.PbMsgBus.ConnR\x04conn\x12\x14\n" +
"\x05topic\x18\x03 \x01(\tR\x05topic\x12\x12\n" +
"\x04data\x18\x04 \x01(\fR\x04data*\x7f\n" +
"\vEnumMsgType\x12\x0f\n" +
"\vMsgTypeNone\x10\x00\x12\x10\n" +
"\fMsgTypeLogin\x10\x01\x12\x11\n" +
"\rMsgTypeLogout\x10\x02\x12\x12\n" +
"\x0eMsgTypeConnect\x10\x03\x12\x15\n" +
"\x11MsgTypeDisconnect\x10\x04\x12\x0f\n" +
"\vMsgTypeData\x10\x05*\xb4\x01\n" +
"\x0fEnumChannelType\x12\x13\n" +
"\x0fChannelTypeNone\x10\x00\x12\x13\n" +
"\x0fChannelTypeMqtt\x10\x01\x12\x12\n" +
"\x0eChannelTypeTcp\x10\x02\x12\x12\n" +
"\x0eChannelTypeUdp\x10\x03\x12\x13\n" +
"\x0fChannelTypeHttp\x10\x04\x12\x11\n" +
"\rChannelTypeWs\x10\x05\x12\x11\n" +
"\rChannelTypeDb\x10\x06\x12\x14\n" +
"\x10ChannelTypeInner\x10d2x\n" +
"\x06MsgBus\x122\n" +
"\x04Send\x12\x14.PbMsgBus.SendMsgReq\x1a\x14.PbMsgBus.SendMsgRes\x12:\n" +
"\bLongSend\x12\x14.PbMsgBus.SendMsgReq\x1a\x14.PbMsgBus.SendMsgRes(\x010\x01B\x05Z\x03pb/b\x06proto3"
var (
file_msgbus_proto_rawDescOnce sync.Once
file_msgbus_proto_rawDescData []byte
)
func file_msgbus_proto_rawDescGZIP() []byte {
file_msgbus_proto_rawDescOnce.Do(func() {
file_msgbus_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_msgbus_proto_rawDesc), len(file_msgbus_proto_rawDesc)))
})
return file_msgbus_proto_rawDescData
}
var file_msgbus_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
var file_msgbus_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
var file_msgbus_proto_goTypes = []any{
(EnumMsgType)(0), // 0: PbMsgBus.EnumMsgType
(EnumChannelType)(0), // 1: PbMsgBus.EnumChannelType
(*Channel)(nil), // 2: PbMsgBus.Channel
(*Conn)(nil), // 3: PbMsgBus.Conn
(*SendMsgReq)(nil), // 4: PbMsgBus.SendMsgReq
(*SendMsgRes)(nil), // 5: PbMsgBus.SendMsgRes
(*HeadReq)(nil), // 6: PbCommon.HeadReq
(*HeadRes)(nil), // 7: PbCommon.HeadRes
}
var file_msgbus_proto_depIdxs = []int32{
6, // 0: PbMsgBus.SendMsgReq.head:type_name -> PbCommon.HeadReq
2, // 1: PbMsgBus.SendMsgReq.channel:type_name -> PbMsgBus.Channel
3, // 2: PbMsgBus.SendMsgReq.conn:type_name -> PbMsgBus.Conn
7, // 3: PbMsgBus.SendMsgRes.head:type_name -> PbCommon.HeadRes
3, // 4: PbMsgBus.SendMsgRes.conn:type_name -> PbMsgBus.Conn
4, // 5: PbMsgBus.MsgBus.Send:input_type -> PbMsgBus.SendMsgReq
4, // 6: PbMsgBus.MsgBus.LongSend:input_type -> PbMsgBus.SendMsgReq
5, // 7: PbMsgBus.MsgBus.Send:output_type -> PbMsgBus.SendMsgRes
5, // 8: PbMsgBus.MsgBus.LongSend:output_type -> PbMsgBus.SendMsgRes
7, // [7:9] is the sub-list for method output_type
5, // [5:7] is the sub-list for method input_type
5, // [5:5] is the sub-list for extension type_name
5, // [5:5] is the sub-list for extension extendee
0, // [0:5] is the sub-list for field type_name
}
func init() { file_msgbus_proto_init() }
func file_msgbus_proto_init() {
if File_msgbus_proto != nil {
return
}
file_common_proto_init()
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_msgbus_proto_rawDesc), len(file_msgbus_proto_rawDesc)),
NumEnums: 2,
NumMessages: 4,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_msgbus_proto_goTypes,
DependencyIndexes: file_msgbus_proto_depIdxs,
EnumInfos: file_msgbus_proto_enumTypes,
MessageInfos: file_msgbus_proto_msgTypes,
}.Build()
File_msgbus_proto = out.File
file_msgbus_proto_goTypes = nil
file_msgbus_proto_depIdxs = nil
}
@@ -0,0 +1,155 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.6.1
// - protoc v5.29.3
// source: msgbus.proto
package pb
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.64.0 or later.
const _ = grpc.SupportPackageIsVersion9
const (
MsgBus_Send_FullMethodName = "/PbMsgBus.MsgBus/Send"
MsgBus_LongSend_FullMethodName = "/PbMsgBus.MsgBus/LongSend"
)
// MsgBusClient is the client API for MsgBus service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type MsgBusClient interface {
Send(ctx context.Context, in *SendMsgReq, opts ...grpc.CallOption) (*SendMsgRes, error)
LongSend(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[SendMsgReq, SendMsgRes], error)
}
type msgBusClient struct {
cc grpc.ClientConnInterface
}
func NewMsgBusClient(cc grpc.ClientConnInterface) MsgBusClient {
return &msgBusClient{cc}
}
func (c *msgBusClient) Send(ctx context.Context, in *SendMsgReq, opts ...grpc.CallOption) (*SendMsgRes, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(SendMsgRes)
err := c.cc.Invoke(ctx, MsgBus_Send_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *msgBusClient) LongSend(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[SendMsgReq, SendMsgRes], error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &MsgBus_ServiceDesc.Streams[0], MsgBus_LongSend_FullMethodName, cOpts...)
if err != nil {
return nil, err
}
x := &grpc.GenericClientStream[SendMsgReq, SendMsgRes]{ClientStream: stream}
return x, nil
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type MsgBus_LongSendClient = grpc.BidiStreamingClient[SendMsgReq, SendMsgRes]
// MsgBusServer is the server API for MsgBus service.
// All implementations must embed UnimplementedMsgBusServer
// for forward compatibility.
type MsgBusServer interface {
Send(context.Context, *SendMsgReq) (*SendMsgRes, error)
LongSend(grpc.BidiStreamingServer[SendMsgReq, SendMsgRes]) error
mustEmbedUnimplementedMsgBusServer()
}
// UnimplementedMsgBusServer must be embedded to have
// forward compatible implementations.
//
// NOTE: this should be embedded by value instead of pointer to avoid a nil
// pointer dereference when methods are called.
type UnimplementedMsgBusServer struct{}
func (UnimplementedMsgBusServer) Send(context.Context, *SendMsgReq) (*SendMsgRes, error) {
return nil, status.Error(codes.Unimplemented, "method Send not implemented")
}
func (UnimplementedMsgBusServer) LongSend(grpc.BidiStreamingServer[SendMsgReq, SendMsgRes]) error {
return status.Error(codes.Unimplemented, "method LongSend not implemented")
}
func (UnimplementedMsgBusServer) mustEmbedUnimplementedMsgBusServer() {}
func (UnimplementedMsgBusServer) testEmbeddedByValue() {}
// UnsafeMsgBusServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to MsgBusServer will
// result in compilation errors.
type UnsafeMsgBusServer interface {
mustEmbedUnimplementedMsgBusServer()
}
func RegisterMsgBusServer(s grpc.ServiceRegistrar, srv MsgBusServer) {
// If the following call panics, it indicates UnimplementedMsgBusServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
t.testEmbeddedByValue()
}
s.RegisterService(&MsgBus_ServiceDesc, srv)
}
func _MsgBus_Send_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SendMsgReq)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(MsgBusServer).Send(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: MsgBus_Send_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(MsgBusServer).Send(ctx, req.(*SendMsgReq))
}
return interceptor(ctx, in, info, handler)
}
func _MsgBus_LongSend_Handler(srv interface{}, stream grpc.ServerStream) error {
return srv.(MsgBusServer).LongSend(&grpc.GenericServerStream[SendMsgReq, SendMsgRes]{ServerStream: stream})
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type MsgBus_LongSendServer = grpc.BidiStreamingServer[SendMsgReq, SendMsgRes]
// MsgBus_ServiceDesc is the grpc.ServiceDesc for MsgBus service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var MsgBus_ServiceDesc = grpc.ServiceDesc{
ServiceName: "PbMsgBus.MsgBus",
HandlerType: (*MsgBusServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "Send",
Handler: _MsgBus_Send_Handler,
},
},
Streams: []grpc.StreamDesc{
{
StreamName: "LongSend",
Handler: _MsgBus_LongSend_Handler,
ServerStreams: true,
ClientStreams: true,
},
},
Metadata: "msgbus.proto",
}
+237
View File
@@ -0,0 +1,237 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.11
// protoc v5.29.3
// source: msgcall.proto
package pb
import (
reflect "reflect"
sync "sync"
unsafe "unsafe"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type MsgReq struct {
state protoimpl.MessageState `protogen:"open.v1"`
Head *HeadReq `protobuf:"bytes,1,opt,name=head,proto3" json:"head,omitempty"`
Service string `protobuf:"bytes,2,opt,name=service,proto3" json:"service,omitempty"`
Method string `protobuf:"bytes,3,opt,name=method,proto3" json:"method,omitempty"`
Data []byte `protobuf:"bytes,4,opt,name=data,proto3" json:"data,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *MsgReq) Reset() {
*x = MsgReq{}
mi := &file_msgcall_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *MsgReq) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*MsgReq) ProtoMessage() {}
func (x *MsgReq) ProtoReflect() protoreflect.Message {
mi := &file_msgcall_proto_msgTypes[0]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use MsgReq.ProtoReflect.Descriptor instead.
func (*MsgReq) Descriptor() ([]byte, []int) {
return file_msgcall_proto_rawDescGZIP(), []int{0}
}
func (x *MsgReq) GetHead() *HeadReq {
if x != nil {
return x.Head
}
return nil
}
func (x *MsgReq) GetService() string {
if x != nil {
return x.Service
}
return ""
}
func (x *MsgReq) GetMethod() string {
if x != nil {
return x.Method
}
return ""
}
func (x *MsgReq) GetData() []byte {
if x != nil {
return x.Data
}
return nil
}
type MsgRes struct {
state protoimpl.MessageState `protogen:"open.v1"`
Head *HeadRes `protobuf:"bytes,1,opt,name=head,proto3" json:"head,omitempty"`
Service string `protobuf:"bytes,2,opt,name=service,proto3" json:"service,omitempty"`
Method string `protobuf:"bytes,3,opt,name=method,proto3" json:"method,omitempty"`
Data []byte `protobuf:"bytes,4,opt,name=data,proto3" json:"data,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *MsgRes) Reset() {
*x = MsgRes{}
mi := &file_msgcall_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *MsgRes) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*MsgRes) ProtoMessage() {}
func (x *MsgRes) ProtoReflect() protoreflect.Message {
mi := &file_msgcall_proto_msgTypes[1]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use MsgRes.ProtoReflect.Descriptor instead.
func (*MsgRes) Descriptor() ([]byte, []int) {
return file_msgcall_proto_rawDescGZIP(), []int{1}
}
func (x *MsgRes) GetHead() *HeadRes {
if x != nil {
return x.Head
}
return nil
}
func (x *MsgRes) GetService() string {
if x != nil {
return x.Service
}
return ""
}
func (x *MsgRes) GetMethod() string {
if x != nil {
return x.Method
}
return ""
}
func (x *MsgRes) GetData() []byte {
if x != nil {
return x.Data
}
return nil
}
var File_msgcall_proto protoreflect.FileDescriptor
const file_msgcall_proto_rawDesc = "" +
"\n" +
"\rmsgcall.proto\x12\tPbMsgCall\x1a\fcommon.proto\"u\n" +
"\x06MsgReq\x12%\n" +
"\x04head\x18\x01 \x01(\v2\x11.PbCommon.HeadReqR\x04head\x12\x18\n" +
"\aservice\x18\x02 \x01(\tR\aservice\x12\x16\n" +
"\x06method\x18\x03 \x01(\tR\x06method\x12\x12\n" +
"\x04data\x18\x04 \x01(\fR\x04data\"u\n" +
"\x06MsgRes\x12%\n" +
"\x04head\x18\x01 \x01(\v2\x11.PbCommon.HeadResR\x04head\x12\x18\n" +
"\aservice\x18\x02 \x01(\tR\aservice\x12\x16\n" +
"\x06method\x18\x03 \x01(\tR\x06method\x12\x12\n" +
"\x04data\x18\x04 \x01(\fR\x04data2r\n" +
"\aMsgCall\x12/\n" +
"\aOneCall\x12\x11.PbMsgCall.MsgReq\x1a\x11.PbMsgCall.MsgRes\x126\n" +
"\n" +
"StreamCall\x12\x11.PbMsgCall.MsgReq\x1a\x11.PbMsgCall.MsgRes(\x010\x01B\x05Z\x03pb/b\x06proto3"
var (
file_msgcall_proto_rawDescOnce sync.Once
file_msgcall_proto_rawDescData []byte
)
func file_msgcall_proto_rawDescGZIP() []byte {
file_msgcall_proto_rawDescOnce.Do(func() {
file_msgcall_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_msgcall_proto_rawDesc), len(file_msgcall_proto_rawDesc)))
})
return file_msgcall_proto_rawDescData
}
var file_msgcall_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_msgcall_proto_goTypes = []any{
(*MsgReq)(nil), // 0: PbMsgCall.MsgReq
(*MsgRes)(nil), // 1: PbMsgCall.MsgRes
(*HeadReq)(nil), // 2: PbCommon.HeadReq
(*HeadRes)(nil), // 3: PbCommon.HeadRes
}
var file_msgcall_proto_depIdxs = []int32{
2, // 0: PbMsgCall.MsgReq.head:type_name -> PbCommon.HeadReq
3, // 1: PbMsgCall.MsgRes.head:type_name -> PbCommon.HeadRes
0, // 2: PbMsgCall.MsgCall.OneCall:input_type -> PbMsgCall.MsgReq
0, // 3: PbMsgCall.MsgCall.StreamCall:input_type -> PbMsgCall.MsgReq
1, // 4: PbMsgCall.MsgCall.OneCall:output_type -> PbMsgCall.MsgRes
1, // 5: PbMsgCall.MsgCall.StreamCall:output_type -> PbMsgCall.MsgRes
4, // [4:6] is the sub-list for method output_type
2, // [2:4] is the sub-list for method input_type
2, // [2:2] is the sub-list for extension type_name
2, // [2:2] is the sub-list for extension extendee
0, // [0:2] is the sub-list for field type_name
}
func init() { file_msgcall_proto_init() }
func file_msgcall_proto_init() {
if File_msgcall_proto != nil {
return
}
file_common_proto_init()
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_msgcall_proto_rawDesc), len(file_msgcall_proto_rawDesc)),
NumEnums: 0,
NumMessages: 2,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_msgcall_proto_goTypes,
DependencyIndexes: file_msgcall_proto_depIdxs,
MessageInfos: file_msgcall_proto_msgTypes,
}.Build()
File_msgcall_proto = out.File
file_msgcall_proto_goTypes = nil
file_msgcall_proto_depIdxs = nil
}
@@ -0,0 +1,155 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.6.1
// - protoc v5.29.3
// source: msgcall.proto
package pb
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.64.0 or later.
const _ = grpc.SupportPackageIsVersion9
const (
MsgCall_OneCall_FullMethodName = "/PbMsgCall.MsgCall/OneCall"
MsgCall_StreamCall_FullMethodName = "/PbMsgCall.MsgCall/StreamCall"
)
// MsgCallClient is the client API for MsgCall service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type MsgCallClient interface {
OneCall(ctx context.Context, in *MsgReq, opts ...grpc.CallOption) (*MsgRes, error)
StreamCall(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[MsgReq, MsgRes], error)
}
type msgCallClient struct {
cc grpc.ClientConnInterface
}
func NewMsgCallClient(cc grpc.ClientConnInterface) MsgCallClient {
return &msgCallClient{cc}
}
func (c *msgCallClient) OneCall(ctx context.Context, in *MsgReq, opts ...grpc.CallOption) (*MsgRes, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(MsgRes)
err := c.cc.Invoke(ctx, MsgCall_OneCall_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *msgCallClient) StreamCall(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[MsgReq, MsgRes], error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &MsgCall_ServiceDesc.Streams[0], MsgCall_StreamCall_FullMethodName, cOpts...)
if err != nil {
return nil, err
}
x := &grpc.GenericClientStream[MsgReq, MsgRes]{ClientStream: stream}
return x, nil
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type MsgCall_StreamCallClient = grpc.BidiStreamingClient[MsgReq, MsgRes]
// MsgCallServer is the server API for MsgCall service.
// All implementations must embed UnimplementedMsgCallServer
// for forward compatibility.
type MsgCallServer interface {
OneCall(context.Context, *MsgReq) (*MsgRes, error)
StreamCall(grpc.BidiStreamingServer[MsgReq, MsgRes]) error
mustEmbedUnimplementedMsgCallServer()
}
// UnimplementedMsgCallServer must be embedded to have
// forward compatible implementations.
//
// NOTE: this should be embedded by value instead of pointer to avoid a nil
// pointer dereference when methods are called.
type UnimplementedMsgCallServer struct{}
func (UnimplementedMsgCallServer) OneCall(context.Context, *MsgReq) (*MsgRes, error) {
return nil, status.Error(codes.Unimplemented, "method OneCall not implemented")
}
func (UnimplementedMsgCallServer) StreamCall(grpc.BidiStreamingServer[MsgReq, MsgRes]) error {
return status.Error(codes.Unimplemented, "method StreamCall not implemented")
}
func (UnimplementedMsgCallServer) mustEmbedUnimplementedMsgCallServer() {}
func (UnimplementedMsgCallServer) testEmbeddedByValue() {}
// UnsafeMsgCallServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to MsgCallServer will
// result in compilation errors.
type UnsafeMsgCallServer interface {
mustEmbedUnimplementedMsgCallServer()
}
func RegisterMsgCallServer(s grpc.ServiceRegistrar, srv MsgCallServer) {
// If the following call panics, it indicates UnimplementedMsgCallServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
t.testEmbeddedByValue()
}
s.RegisterService(&MsgCall_ServiceDesc, srv)
}
func _MsgCall_OneCall_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(MsgReq)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(MsgCallServer).OneCall(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: MsgCall_OneCall_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(MsgCallServer).OneCall(ctx, req.(*MsgReq))
}
return interceptor(ctx, in, info, handler)
}
func _MsgCall_StreamCall_Handler(srv interface{}, stream grpc.ServerStream) error {
return srv.(MsgCallServer).StreamCall(&grpc.GenericServerStream[MsgReq, MsgRes]{ServerStream: stream})
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type MsgCall_StreamCallServer = grpc.BidiStreamingServer[MsgReq, MsgRes]
// MsgCall_ServiceDesc is the grpc.ServiceDesc for MsgCall service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var MsgCall_ServiceDesc = grpc.ServiceDesc{
ServiceName: "PbMsgCall.MsgCall",
HandlerType: (*MsgCallServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "OneCall",
Handler: _MsgCall_OneCall_Handler,
},
},
Streams: []grpc.StreamDesc{
{
StreamName: "StreamCall",
Handler: _MsgCall_StreamCall_Handler,
ServerStreams: true,
ClientStreams: true,
},
},
Metadata: "msgcall.proto",
}
+197
View File
@@ -0,0 +1,197 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.11
// protoc v5.29.3
// source: notice.proto
package pb
import (
reflect "reflect"
sync "sync"
unsafe "unsafe"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type SendReq struct {
state protoimpl.MessageState `protogen:"open.v1"`
Head *HeadReq `protobuf:"bytes,1,opt,name=head,proto3" json:"head,omitempty"`
Config []byte `protobuf:"bytes,2,opt,name=config,proto3" json:"config,omitempty"`
Params []byte `protobuf:"bytes,3,opt,name=params,proto3" json:"params,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *SendReq) Reset() {
*x = SendReq{}
mi := &file_notice_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *SendReq) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SendReq) ProtoMessage() {}
func (x *SendReq) ProtoReflect() protoreflect.Message {
mi := &file_notice_proto_msgTypes[0]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SendReq.ProtoReflect.Descriptor instead.
func (*SendReq) Descriptor() ([]byte, []int) {
return file_notice_proto_rawDescGZIP(), []int{0}
}
func (x *SendReq) GetHead() *HeadReq {
if x != nil {
return x.Head
}
return nil
}
func (x *SendReq) GetConfig() []byte {
if x != nil {
return x.Config
}
return nil
}
func (x *SendReq) GetParams() []byte {
if x != nil {
return x.Params
}
return nil
}
type SendRes struct {
state protoimpl.MessageState `protogen:"open.v1"`
Head *HeadRes `protobuf:"bytes,1,opt,name=head,proto3" json:"head,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *SendRes) Reset() {
*x = SendRes{}
mi := &file_notice_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *SendRes) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SendRes) ProtoMessage() {}
func (x *SendRes) ProtoReflect() protoreflect.Message {
mi := &file_notice_proto_msgTypes[1]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SendRes.ProtoReflect.Descriptor instead.
func (*SendRes) Descriptor() ([]byte, []int) {
return file_notice_proto_rawDescGZIP(), []int{1}
}
func (x *SendRes) GetHead() *HeadRes {
if x != nil {
return x.Head
}
return nil
}
var File_notice_proto protoreflect.FileDescriptor
const file_notice_proto_rawDesc = "" +
"\n" +
"\fnotice.proto\x12\bPbNotice\x1a\fcommon.proto\"`\n" +
"\aSendReq\x12%\n" +
"\x04head\x18\x01 \x01(\v2\x11.PbCommon.HeadReqR\x04head\x12\x16\n" +
"\x06config\x18\x02 \x01(\fR\x06config\x12\x16\n" +
"\x06params\x18\x03 \x01(\fR\x06params\"0\n" +
"\aSendRes\x12%\n" +
"\x04head\x18\x01 \x01(\v2\x11.PbCommon.HeadResR\x04head28\n" +
"\x06Notice\x12.\n" +
"\x04Send\x12\x11.PbNotice.SendReq\x1a\x11.PbNotice.SendRes\"\x00B\x05Z\x03pb/b\x06proto3"
var (
file_notice_proto_rawDescOnce sync.Once
file_notice_proto_rawDescData []byte
)
func file_notice_proto_rawDescGZIP() []byte {
file_notice_proto_rawDescOnce.Do(func() {
file_notice_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_notice_proto_rawDesc), len(file_notice_proto_rawDesc)))
})
return file_notice_proto_rawDescData
}
var file_notice_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_notice_proto_goTypes = []any{
(*SendReq)(nil), // 0: PbNotice.SendReq
(*SendRes)(nil), // 1: PbNotice.SendRes
(*HeadReq)(nil), // 2: PbCommon.HeadReq
(*HeadRes)(nil), // 3: PbCommon.HeadRes
}
var file_notice_proto_depIdxs = []int32{
2, // 0: PbNotice.SendReq.head:type_name -> PbCommon.HeadReq
3, // 1: PbNotice.SendRes.head:type_name -> PbCommon.HeadRes
0, // 2: PbNotice.Notice.Send:input_type -> PbNotice.SendReq
1, // 3: PbNotice.Notice.Send:output_type -> PbNotice.SendRes
3, // [3:4] is the sub-list for method output_type
2, // [2:3] is the sub-list for method input_type
2, // [2:2] is the sub-list for extension type_name
2, // [2:2] is the sub-list for extension extendee
0, // [0:2] is the sub-list for field type_name
}
func init() { file_notice_proto_init() }
func file_notice_proto_init() {
if File_notice_proto != nil {
return
}
file_common_proto_init()
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_notice_proto_rawDesc), len(file_notice_proto_rawDesc)),
NumEnums: 0,
NumMessages: 2,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_notice_proto_goTypes,
DependencyIndexes: file_notice_proto_depIdxs,
MessageInfos: file_notice_proto_msgTypes,
}.Build()
File_notice_proto = out.File
file_notice_proto_goTypes = nil
file_notice_proto_depIdxs = nil
}
@@ -0,0 +1,122 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.6.1
// - protoc v5.29.3
// source: notice.proto
package pb
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.64.0 or later.
const _ = grpc.SupportPackageIsVersion9
const (
Notice_Send_FullMethodName = "/PbNotice.Notice/Send"
)
// NoticeClient is the client API for Notice service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type NoticeClient interface {
Send(ctx context.Context, in *SendReq, opts ...grpc.CallOption) (*SendRes, error)
}
type noticeClient struct {
cc grpc.ClientConnInterface
}
func NewNoticeClient(cc grpc.ClientConnInterface) NoticeClient {
return &noticeClient{cc}
}
func (c *noticeClient) Send(ctx context.Context, in *SendReq, opts ...grpc.CallOption) (*SendRes, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(SendRes)
err := c.cc.Invoke(ctx, Notice_Send_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
// NoticeServer is the server API for Notice service.
// All implementations must embed UnimplementedNoticeServer
// for forward compatibility.
type NoticeServer interface {
Send(context.Context, *SendReq) (*SendRes, error)
mustEmbedUnimplementedNoticeServer()
}
// UnimplementedNoticeServer must be embedded to have
// forward compatible implementations.
//
// NOTE: this should be embedded by value instead of pointer to avoid a nil
// pointer dereference when methods are called.
type UnimplementedNoticeServer struct{}
func (UnimplementedNoticeServer) Send(context.Context, *SendReq) (*SendRes, error) {
return nil, status.Error(codes.Unimplemented, "method Send not implemented")
}
func (UnimplementedNoticeServer) mustEmbedUnimplementedNoticeServer() {}
func (UnimplementedNoticeServer) testEmbeddedByValue() {}
// UnsafeNoticeServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to NoticeServer will
// result in compilation errors.
type UnsafeNoticeServer interface {
mustEmbedUnimplementedNoticeServer()
}
func RegisterNoticeServer(s grpc.ServiceRegistrar, srv NoticeServer) {
// If the following call panics, it indicates UnimplementedNoticeServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
t.testEmbeddedByValue()
}
s.RegisterService(&Notice_ServiceDesc, srv)
}
func _Notice_Send_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SendReq)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(NoticeServer).Send(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Notice_Send_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(NoticeServer).Send(ctx, req.(*SendReq))
}
return interceptor(ctx, in, info, handler)
}
// Notice_ServiceDesc is the grpc.ServiceDesc for Notice service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var Notice_ServiceDesc = grpc.ServiceDesc{
ServiceName: "PbNotice.Notice",
HandlerType: (*NoticeServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "Send",
Handler: _Notice_Send_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "notice.proto",
}
@@ -0,0 +1,32 @@
package initconsul
import (
"context"
"github.com/gogf/gf/contrib/registry/consul/v2"
"github.com/gogf/gf/contrib/rpc/grpcx/v2"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/gsel"
)
func InitConsul(ctx context.Context) error {
// 初始化consul
err := registerConsul(ctx)
if err != nil {
return err
}
return nil
}
func registerConsul(ctx context.Context) error {
consulAddress := g.Cfg().MustGet(ctx, "registry.address", "127.0.0.1:8500").String()
g.Log().Infof(ctx, "consulAddress: %+v", consulAddress)
registry, err := consul.New(consul.WithAddress(consulAddress))
if err != nil {
g.Log().Fatalf(ctx, "consul初始化失败: %+v", err)
return err
}
grpcx.Resolver.Register(registry)
gsel.SetBuilder(gsel.NewBuilderRandom())
g.Log().Infof(ctx, "consul注册成功")
return nil
}
@@ -0,0 +1,51 @@
// package version
// @Link https://github.com/xiujiecn/xiu-admin
// @Copyright Copyright (c) 2025 LiXiujie
// @Author Lxj <xxx@example.com>
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
package version
import (
"context"
"fmt"
"github.com/gogf/gf/v2/frame/g"
)
var (
BuildTime string
BuildVersion string
CommitID string
)
func ShowLogo(ctx context.Context, buildVersion, buildTime, commitID string) {
BuildVersion = buildVersion
BuildTime = buildTime
CommitID = commitID
//版本号
g.Log().Notice(ctx, `
██╗██╗███████╗ ██╗ ██████╗ ████████╗
██║██║██╔════╝ ██║██╔═══██╗╚══██╔══╝
██║██║█████╗ ██║██║ ██║ ██║
██ ██║██║██╔══╝ ██║██║ ██║ ██║
╚█████╔╝██║███████╗ ██║╚██████╔╝ ██║
╚════╝ ╚═╝╚══════╝ ╚═╝ ╚═════╝ ╚═╝
`)
g.Log().Notice(ctx, "Version :", buildVersion)
g.Log().Notice(ctx, "BuildTime :", buildTime)
g.Log().Notice(ctx, "CommitID :", commitID)
g.Log().Notice(ctx, "Copyright:", "2026 The iotLCM Authors")
fmt.Println("Version :", buildVersion)
fmt.Println("BuildTime :", buildTime)
fmt.Println("CommitID :", commitID)
fmt.Println("Copyright:", "2026 The iotLCM Authors")
}
func AppName(ctx context.Context) string {
return g.Cfg().MustGet(ctx, "system.appName", "IotLCM").String()
}
@@ -0,0 +1,14 @@
// Package xjpubconsts
// @Link https://github.com/xiujiecn/xiu-admin
// @Copyright Copyright (c) 2025 LiXiujie
// @Author Lxj <xxx@example.com>
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
package xjpubconsts
const (
HTTPContentTypeXml = "text/xml"
HTTPContentTypeHtml = "text/html"
HTTPContentTypeStream = "text/event-stream"
HTTPContentTypeJson = "application/json"
HTTPContentTypeExcel = "application/octet-stream"
)
@@ -0,0 +1,17 @@
package xjpubconsts
const (
ServiceTypeWeb = "web" // web服务
ServiceTypeQueue = "queue" // 队列服务
ServiceTypeRedis = "redis" // 缓存类型
ServiceTypeNotice = "notice" // 通知服务
ServiceTypeTask = "task" // 任务服务
ServiceTypeAccess = "access" // 接入服务
)
const (
ServiceNameYzAdminWeb = "yzadmin-web-svc" // 后台管理服务
ServiceNameYzUpstreamChanSvc = "yzupstreamchan-grpc-svc" // 上游通道服务
ServiceNameYzUpstreamProxySvc = "yzupstreamproxy-grpc-svc" // 上游代理服务
ServiceNameYzApiHttpSvc = "yzapi-http-svc" // API 服务
)
@@ -0,0 +1,100 @@
package httpclient
import (
"context"
"git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/pub/xjpubservice"
"github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/gclient"
"github.com/gogf/gf/v2/net/gsvc"
)
type sHttpClient struct {
client *gclient.Client
}
func NewHttpClient() *sHttpClient {
client := g.Client()
return &sHttpClient{
client: client,
}
}
func init() {
xjpubservice.RegisterHttpClient(NewHttpClient())
}
func (s *sHttpClient) PostObj(ctx context.Context, serviceName string, path string, req interface{}, res interface{}) (err error) {
reqJson, err := gjson.Marshal(req)
if err != nil {
return err
}
resJson, err := s.Post(ctx, serviceName, path, reqJson)
if err != nil {
return err
}
err = gjson.Unmarshal(resJson, res)
if err != nil {
g.Log().Errorf(ctx, "sHttpClient.PostObj gjson.Unmarshal error: %v, resJson: %s", err, string(resJson))
return err
}
g.Log().Debugf(ctx, "sHttpClient.PostObj res: %+v, resJson: %s", res, string(resJson))
return nil
}
func (s *sHttpClient) Post(ctx context.Context, serviceName string, path string, req []byte) (res []byte, err error) {
// s.client.SetHeader("Span-Id", "0.0.1")
// s.client.SetHeader("Trace-Id", gctx.CtxId(ctx))
url := "http://" + serviceName + path
s.client.SetDiscovery(gsvc.GetRegistry())
// g.Log().Debugf(ctx, "sHttpClient.Post url: %s, req: %+v", url, string(req))
response, err := s.client.Post(ctx, url, req)
if err != nil {
g.Log().Errorf(ctx, "sHttpClient.Post Post error: %v, url: %s, req: %+v", err, url, string(req))
return nil, err
}
defer response.Close()
res = response.ReadAll()
g.Log().Debugf(ctx, "sHttpClient.Post url: %s, req: %+v, res: %+v", url, string(req), string(res))
return res, nil
}
func (s *sHttpClient) AllServerPost(ctx context.Context, serviceName string, path string, req []byte) (resMap map[string][]byte, err error) {
// s.client.SetHeader("Span-Id", "0.0.1")
// s.client.SetHeader("Trace-Id", gctx.CtxId(ctx))
s.client.SetDiscovery(gsvc.GetRegistry())
servers, err := gsvc.Search(ctx, gsvc.SearchInput{
Name: serviceName,
})
if len(servers) == 0 {
g.Log().Errorf(ctx, "sHttpClient.AllServerPost gsvc.Search(ctx, serviceName) error: %v, serviceName: %s", err, serviceName)
return nil, err
}
// g.Log().Debugf(ctx, "sHttpClient.AllServerPost servers: %+v", servers)
endpoints := make([]string, 0)
for _, server := range servers {
endpoint := server.GetEndpoints()
if len(endpoint) == 0 {
g.Log().Errorf(ctx, "sHttpClient.AllServerPost server.GetEndpoints() error: %v, serviceName: %s", err, serviceName)
continue
}
endpoints = append(endpoints, endpoint[0].String())
}
resMap = make(map[string][]byte)
for _, e := range endpoints {
url := "http://" + e + path
res, err := s.Post(ctx, e, path, req)
// g.Log().Debugf(ctx, "sHttpClient.AllServerPost Post url: %s, req: %+v res: %+v err: %+v", url, string(req), string(res), err)
if err != nil {
resMap[e] = nil
g.Log().Errorf(ctx, "sHttpClient.AllServerPost Post error: %v, url: %s, req: %+v", err, url, string(req))
continue
}
resMap[e] = res
}
return resMap, nil
}
@@ -0,0 +1,37 @@
package httpclient
import (
"context"
"git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/pub/xjpubconsts"
"git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/pub/xjpubmodel"
"git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/pub/xjpubmodel/response"
"git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/pub/xjpubservice"
)
type sUpstreamProxyClient struct {
}
func NewUpstreamProxyClient() *sUpstreamProxyClient {
return &sUpstreamProxyClient{}
}
func init() {
xjpubservice.RegisterUpstreamProxyClient(NewUpstreamProxyClient())
}
// 物联网卡在线信息实时查询
func (s *sUpstreamProxyClient) QuerySimSession(ctx context.Context, input *xjpubmodel.SimSessionInput) (output *xjpubmodel.SimSessionOutput, err error) {
res := &response.ResponseGeneric[*xjpubmodel.SimSessionOutput]{}
err = xjpubservice.HttpClient().PostObj(ctx, xjpubconsts.ServiceNameYzUpstreamChanSvc, "/query/sim-session", input, res)
output = res.Data
return output, err
}
// 物联网卡卡状态查询
func (s *sUpstreamProxyClient) QuerySimCardStatus(ctx context.Context, input *xjpubmodel.SimCardStatusInput) (output *xjpubmodel.SimCardStatusOutput, err error) {
res := &response.ResponseGeneric[*xjpubmodel.SimCardStatusOutput]{}
err = xjpubservice.HttpClient().PostObj(ctx, xjpubconsts.ServiceNameYzUpstreamChanSvc, "/query/sim-status", input, res)
output = res.Data
return output, err
}
@@ -0,0 +1,68 @@
package info
import (
"git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/pub/xjpubmodel"
"git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/pub/xjpubservice"
)
type sInfo struct {
Info xjpubmodel.InfoModel
}
func NewInfo() *sInfo {
return &sInfo{
Info: xjpubmodel.InfoModel{},
}
}
func init() {
xjpubservice.RegisterInfo(NewInfo())
}
func (s *sInfo) GetInfo() *xjpubmodel.InfoModel {
return &s.Info
}
func (s *sInfo) SetInfoName(Type, Name, Title, Description string) {
if s.Info.Name == "" {
s.Info.Type = Type
s.Info.Name = Name
s.Info.Title = Title
s.Info.Description = Description
}
}
func (s *sInfo) SetInfoBuild(buildVersion, buildTime, commitID string) {
s.Info.Version = buildVersion
s.Info.Build = buildTime
s.Info.Commit = commitID
}
func (s *sInfo) SetInfoData(data map[string]interface{}) {
s.Info.Data = data
}
func (s *sInfo) GetInfoData() map[string]interface{} {
return s.Info.Data
}
func (s *sInfo) AddInfoData(key string, value interface{}) {
if s.Info.Data == nil {
s.Info.Data = make(map[string]interface{})
}
s.Info.Data[key] = value
}
func (s *sInfo) DelInfoData(key string) {
delete(s.Info.Data, key)
}
func (s *sInfo) GetInfoDataByKey(key string) interface{} {
if s.Info.Data == nil {
return nil
}
if _, ok := s.Info.Data[key]; !ok {
return nil
}
return s.Info.Data[key]
}
@@ -0,0 +1,127 @@
package info
import (
"context"
"runtime"
"git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/pub/xjpubmodel"
"git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/pub/xjpubservice"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/glog"
)
type sInfoRpc struct {
Info *xjpubmodel.InfoModel
}
func NewInfoRpc() *sInfoRpc {
return &sInfoRpc{
Info: xjpubmodel.NewInfoModel(),
}
}
func init() {
xjpubservice.RegisterInfoRpc(NewInfoRpc())
}
func (s *sInfoRpc) GetInfo() *xjpubmodel.InfoModel {
ctx := context.Background()
logLevel := g.Cfg().MustGet(ctx, "logger.level", "").String()
if logLevel != "" {
g.Log().SetLevelStr(logLevel)
}
s.AddInfoData("log_level_conf", logLevel)
s.AddInfoData("log_level_real", toLogLevelStr(g.Log().GetLevel()))
s.AddInfoData("Goroutines", runtime.NumGoroutine())
var m runtime.MemStats
runtime.ReadMemStats(&m)
s.AddInfoData("MemAllocKB", m.Alloc/1024)
s.AddInfoData("MemTotalAllocKB", m.TotalAlloc/1024)
s.AddInfoData("MemSysKB", m.Sys/1024)
s.AddInfoData("MemHeapAllocKB", m.HeapAlloc/1024)
s.AddInfoData("MemHeapSysKB", m.HeapSys/1024)
s.AddInfoData("MemHeapIdleKB", m.HeapIdle/1024)
s.AddInfoData("MemHeapInuseKB", m.HeapInuse/1024)
s.AddInfoData("MemHeapReleasedKB", m.HeapReleased/1024)
s.AddInfoData("MemHeapObjects", m.HeapObjects)
s.AddInfoData("MemStackInuseKB", m.StackInuse/1024)
s.AddInfoData("MemStackSysKB", m.StackSys/1024)
s.AddInfoData("MemNumGC", m.NumGC)
return s.Info
}
func toLogLevelStr(level int) string {
if level == glog.LEVEL_NONE {
return "NONE"
}
if level == glog.LEVEL_ALL {
return "ALL"
}
if level == glog.LEVEL_DEV {
return "DEV"
}
if level == glog.LEVEL_PROD {
return "PROD"
}
str := ""
if level&glog.LEVEL_DEBU != 0 {
str += "DEBUG "
}
if level&glog.LEVEL_INFO != 0 {
str += "INFO "
}
if level&glog.LEVEL_NOTI != 0 {
str += "NOTI "
}
if level&glog.LEVEL_WARN != 0 {
str += "WARN "
}
if level&glog.LEVEL_ERRO != 0 {
str += "ERRO "
}
if level&glog.LEVEL_CRIT != 0 {
str += "CRIT "
}
if level&glog.LEVEL_PANI != 0 {
str += "PANI "
}
if level&glog.LEVEL_FATA != 0 {
str += "FATA "
}
return str
}
func (s *sInfoRpc) SetInfoName(Type, Name, Title, Description string) {
if s.Info.Name == "" {
s.Info.Type = Type
s.Info.Name = Name
s.Info.Title = Title
s.Info.Description = Description
}
}
func (s *sInfoRpc) SetInfoBuild(buildVersion, buildTime, commitID string) {
s.Info.Version = buildVersion
s.Info.Build = buildTime
s.Info.Commit = commitID
}
func (s *sInfoRpc) SetInfoData(data map[string]interface{}) {
s.Info.Data = data
}
func (s *sInfoRpc) GetInfoData() map[string]interface{} {
return s.Info.Data
}
func (s *sInfoRpc) AddInfoData(key string, value interface{}) {
s.Info.Data[key] = value
}
func (s *sInfoRpc) DelInfoData(key string) {
delete(s.Info.Data, key)
}
func (s *sInfoRpc) GetInfoDataByKey(key string) interface{} {
return s.Info.Data[key]
}
@@ -0,0 +1,118 @@
// package middleware
// @Link https://github.com/xiujiecn/xiu-admin
// @Copyright Copyright (c) 2025 LiXiujie
// @Author Lxj <xxx@example.com>
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
package middleware
import (
"context"
"net/http"
"slices"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/util/gmeta"
"git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/pub/xjpubconsts"
"git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/pub/xjpubmodel/response"
"git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/pub/xjpubservice"
)
type sMiddleware struct {
}
func New() *sMiddleware {
return &sMiddleware{}
}
func init() {
xjpubservice.RegisterMiddleware(New())
}
func getContentType(r *ghttp.Request) (contentType string) {
contentType = r.Response.Header().Get("Content-Type")
if contentType != "" {
return
}
mime := gmeta.Get(r.GetHandlerResponse(), "mime").String()
if mime == "" {
contentType = xjpubconsts.HTTPContentTypeJson
} else {
contentType = mime
}
return
}
// 响应处理中间件
func (m *sMiddleware) ResponseHandler(r *ghttp.Request) {
r.Middleware.Next()
if r.Response.BufferLength() > 0 {
return
}
contentType := getContentType(r)
if contentType == xjpubconsts.HTTPContentTypeExcel {
r.Response.Status = http.StatusOK
return
}
// g.Log().Infof(r.GetCtx(), "sMiddleware.ResponseHandler,contentType %v", contentType)
err := r.GetError()
res := r.GetHandlerResponse()
var code gcode.Code = gcode.CodeOK
if err != nil {
code = gerror.Code(err)
if code == gcode.CodeNil {
code = gcode.CodeInternalError
}
response.JsonExit(r, code.Code(), err.Error())
} else {
response.Json(r, code.Code(), "", res)
}
}
// CORS 跨域处理
func (s *sMiddleware) CORS(r *ghttp.Request) {
r.SetCtx(r.GetNeverDoneCtx())
corsOptions := r.Response.DefaultCORSOptions()
corsConfig := g.Cfg().MustGet(context.Background(), "server.allowedDomains").Strings()
if len(corsConfig) == 0 {
r.Response.CORSDefault()
} else {
corsOptions.AllowDomain = corsConfig
r.Response.CORS(corsOptions)
}
r.Middleware.Next()
}
// IsExceptLogin 判断是否需要验证登录
func (s *sMiddleware) IsExceptLogin(ctx context.Context, path string) bool {
pathList := g.Cfg().MustGet(ctx, "router.exceptLogin").Strings()
return slices.Contains(pathList, path)
}
// ResponseHandlerRaw 返回处理中间件
func (s *sMiddleware) ResponseHandlerRaw(r *ghttp.Request) {
r.Middleware.Next()
// 如果已经有返回内容,那么该中间件什么也不做
if r.Response.BufferLength() > 0 {
g.Log().Infof(r.Context(), "ResponseHandler r.Response.BufferLength() %d buf:%s", r.Response.BufferLength(), r.Response.BufferString())
return
}
var (
err = r.GetError()
res = r.GetHandlerResponse()
)
if err != nil {
r.Response.WriteJson(res)
r.ExitAll()
} else {
r.Response.WriteJson(res)
}
}
@@ -0,0 +1,11 @@
// ==========================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// ==========================================================================
package xjpublogic
import (
_ "git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/pub/xjpublogic/httpclient"
_ "git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/pub/xjpublogic/info"
_ "git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/pub/xjpublogic/middleware"
)
@@ -0,0 +1,18 @@
package xjpubmodel
type InfoModel struct {
Type string `json:"type"`
Name string `json:"name"`
Title string `json:"title"`
Description string `json:"description"`
Version string `json:"version"`
Build string `json:"build"`
Commit string `json:"commit"`
Data map[string]interface{} `json:"data"`
}
func NewInfoModel() *InfoModel {
return &InfoModel{
Data: make(map[string]interface{}),
}
}
@@ -0,0 +1,12 @@
// package response
// @Link https://github.com/xiujiecn/xiu-admin
// @Copyright Copyright (c) 2025 LiXiujie
// @Author Lxj <xxx@example.com>
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
package response
type PageResult struct {
Page int `json:"page"`
PageSize int `json:"pageSize"`
Total int `json:"total"`
}
@@ -0,0 +1,67 @@
// package response
// @Link https://github.com/xiujiecn/xiu-admin
// @Copyright Copyright (c) 2025 LiXiujie
// @Author Lxj <xxx@example.com>
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
package response
import (
"fmt"
"io"
"net/http"
"time"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/os/gtime"
)
// 泛型
type ResponseGeneric[T any] struct {
Code int `json:"code"`
Message string `json:"message"`
Data T `json:"data"`
}
type JsonResponse struct {
Code int `json:"code"`
Message string `json:"message"`
Data interface{} `json:"data"`
Error interface{} `json:"error"`
}
func Json(r *ghttp.Request, code int, message string, data ...interface{}) {
var responseData interface{}
if len(data) > 0 {
responseData = data[0]
} else {
responseData = g.Map{}
}
// g.Log().Infof(r.GetCtx(), "response.Json: %v", responseData)
r.Response.WriteJson(JsonResponse{
Code: code,
Message: message,
Data: responseData,
})
}
func JsonExit(r *ghttp.Request, code int, message string, data ...interface{}) {
Json(r, code, message, data...)
r.ExitAll()
}
// 向前端返回Excel文件 参数 content 为上面生成的io.ReadSeeker, fileTag 为返回前端的文件名
func Excel(r *ghttp.Request, content io.ReadSeeker, fileTag string) {
fileName := fmt.Sprintf("%s%s%s.xlsx", gtime.Now().Format("Ymdhis"), `_`, fileTag)
r.Response.Header().Set("Content-Type", "application/vnd.ms-excel")
r.Response.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", fileName))
http.ServeContent(r.Response.Writer, r.Request, fileTag, time.Now(), content)
}
// 向前端返回Json文件 参数 content 为上面生成的io.ReadSeeker, fileTag 为返回前端的文件名
func JsonFile(r *ghttp.Request, content io.ReadSeeker, fileTag string) {
fileName := fmt.Sprintf("%s%s%s.json", gtime.Now().Format("Ymdhis"), `_`, fileTag)
r.Response.Header().Set("Content-Type", "application/json")
r.Response.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", fileName))
http.ServeContent(r.Response.Writer, r.Request, fileTag, time.Now(), content)
}
@@ -0,0 +1,48 @@
package xjpubmodel
type SimKey struct {
ChanKey string `json:"chanKey" dc:"通道标识"`
ICCID string `json:"iccid" dc:"联网卡集成电路卡识别码20位"`
}
// 物联网卡在线信息实时查询 请求参数
type SimSessionInput struct {
SimKeys []*SimKey `json:"simKeys" dc:"SIM卡标识列表"`
}
type SimSessionItem struct {
*SimKey
ApnId string `json:"apnId" dc:"APN ID"`
Rat string `json:"rat" dc:"接入方式"`
Ipv4 string `json:"ipv4" dc:"IPV4"`
Ipv6 string `json:"ipv6" dc:"IPV6"`
Ipv6Prefix string `json:"ipv6Prefix" dc:"IPV6前缀"`
Status string `json:"status" dc:"状态(0:用户在线 -1:用户不在线 -2:未查询到会话信息)"`
CreateTime string `json:"createTime" dc:"创建时间"`
ApiStatus int `json:"apiStatus" dc:"API状态"`
ApiMessage string `json:"apiMessage" dc:"API消息"`
}
// 物联网卡在线信息实时查询 响应参数
type SimSessionOutput struct {
Items []*SimSessionItem `json:"items" dc:"SIM卡会话列表"`
}
// 物联网卡卡状态查询 请求参数
type SimCardStatusInput struct {
SimKeys []*SimKey `json:"simKeys" dc:"SIM卡标识列表"`
}
// 物联网卡卡状态查询 响应参数
type SimCardStatusOutput struct {
Items []*SimCardStatusItem `json:"items" dc:"SIM卡状态列表"`
}
// {"cardStatus":"2","lastChangeDate":"2024-05-30 22:05:31"}]
type SimCardStatusItem struct {
*SimKey
CardStatus int `json:"cardStatus" dc:"卡状态"`
LastChangeDate string `json:"lastChangeDate" dc:"最后变更时间"`
ApiStatus int `json:"apiStatus" dc:"API状态"`
ApiMessage string `json:"apiMessage" dc:"API消息"`
}
@@ -0,0 +1,91 @@
package xjpubmodel
import (
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/util/gconv"
)
type MetricItem struct {
Name string `json:"name"`
Value float64 `json:"value"`
Time *gtime.Time `json:"time"`
}
func (m *MetricItem) SetValue(value interface{}) {
m.Value = gconv.Float64(value)
m.Time = gtime.Now()
}
func (m *MetricItem) GetValue() float64 {
return m.Value
}
func (m *MetricItem) GetTime() *gtime.Time {
return m.Time
}
func (m *MetricItem) GetName() string {
return m.Name
}
func (m *MetricItem) SetName(name string) {
m.Name = name
}
func (m *MetricItem) AddValue(value interface{}) {
m.Value += gconv.Float64(value)
m.Time = gtime.Now()
}
func (m *MetricItem) Inc() {
m.Value++
m.Time = gtime.Now()
}
func (m *MetricItem) Dec() {
m.Value--
m.Time = gtime.Now()
}
type MetricData struct {
MetricClientCount MetricItem `json:"clientCount"` // 当前在线数
MetricConnectedCounter MetricItem `json:"connectedCounter"` // 连接数
MetricDisconnectedCounter MetricItem `json:"disconnectedCounter"` // 断开连接数
MetricRecvPkgCount MetricItem `json:"recvPkgCount"` // 接收包数
MetricSendPkgCount MetricItem `json:"sendPkgCount"` // 发送包数
MetricRecvPkgSize MetricItem `json:"recvPkgSize"` // 接收包大小
MetricSendPkgSize MetricItem `json:"sendPkgSize"` // 发送包大小
MetricRecvPkgTime MetricItem `json:"recvPkgTime"` // 接收包时间(毫秒)
MetricSendPkgTime MetricItem `json:"sendPkgTime"` // 发送包时间(毫秒)
MetricSubTopicCount MetricItem `json:"subTopicCount"` // 订阅主题数
MetricAuthSuccessCount MetricItem `json:"authSuccessCount"` // 认证成功数
MetricAuthFailCount MetricItem `json:"authFailCount"` // 认证失败数
MetricAuthCostTime MetricItem `json:"authCostTime"` // 认证耗时
MetricUpMsgConnectCostTime MetricItem `json:"upMsgConnectCostTime"` // 上行消息连接耗时(毫秒)
MetricUpMsgDataCostTime MetricItem `json:"upMsgDataCostTime"` // 上行消息数据耗时(毫秒)
MetricUpMsgDisconnectCostTime MetricItem `json:"upMsgDisconnectCostTime"` // 上行消息断开连接耗时(毫秒)
MetricTopicCount MetricItem `json:"topicCount"` // 累计主题数
MetricProcessMemSize MetricItem `json:"processMemSize"` // 进程内存大小
MetricProcessCpuUsage MetricItem `json:"processCpuUsage"` // 进程CPU使用率
MetricProcessThreads MetricItem `json:"processThreads"` // 进程线程数
MetricProcessGoroutines MetricItem `json:"processGoroutines"` // 进程协程数
MetricSysCpuNum MetricItem `json:"sysCpuNum"` // 系统CPU数量
MetricSysCpuCores MetricItem `json:"sysCpuCores"` // 系统CPU核数
MetricSysCpuUsage MetricItem `json:"sysCpuUsage"` // 系统CPU使用率
MetricSysCpuLoad1 MetricItem `json:"sysCpuLoad1"` // 系统CPU负载1
MetricSysCpuLoad5 MetricItem `json:"sysCpuLoad5"` // 系统CPU负载2
MetricSysCpuLoad15 MetricItem `json:"sysCpuLoad15"` // 系统CPU负载3
MetricSysMemTotal MetricItem `json:"sysMemTotal"` // 系统内存总量
MetricSysMemUsage MetricItem `json:"sysMemUsage"` // 系统内存使用率
MetricSysDiskTotal MetricItem `json:"sysDiskTotal"` // 系统磁盘总量
MetricSysDiskUsage MetricItem `json:"sysDiskUsage"` // 系统磁盘使用率
MetricWaitMsgCount MetricItem `json:"waitMsgCount"` // 等待消息数量
MetricOneDeviceWaitMsgCount MetricItem `json:"oneDeviceWaitMsgCount"` // 同一个设备等待消息数量
MetricRecvMsgFailCount MetricItem `json:"recvMsgFailCount"` // 接收消息处理失败数
MetricRecvMsgWaitCostTime MetricItem `json:"recvMsgWaitCostTime"` // 接收消息等待耗时
}
type XjServerInfo struct {
NodeName string `json:"nodeName"`
SvrType int `json:"svrType"`
SvrCode string `json:"svrCode"` // 服务器编号
ShardId int64 `json:"shardId"` // 分片ID
StartTime *gtime.Time `json:"startTime"` // 启动时间
SysIpLan string `json:"sysIpLan"` // 内网IP
SysIpWan string `json:"sysIpWan"` // 外网IP
AppVersion string `json:"appVersion"` // 程序版本
TenantId string `json:"tenantId"` // 租户ID
ReportInterval int64 `json:"reportInterval"` // 上报间隔
}

Some files were not shown because too many files have changed in this diff Show More