From 82466d587de3d269cc688a93df3c4a67fd4b7972 Mon Sep 17 00:00:00 2001 From: MiFaZhan Date: Thu, 22 Oct 2026 15:46:43 +0800 Subject: [PATCH] =?UTF-8?q?chore:=20=E5=88=9D=E5=A7=8B=E5=BC=80=E6=BA=90?= =?UTF-8?q?=E5=8F=91=E5=B8=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit iotlcm-upstream-proxy:渠道缓存与转发网关。 - 内联原私有依赖:yunze-public → internal/pub/(18 文件), yunze-pb/pb → internal/pb/(9 文件);Consul 注册改用官方 gogf/gf/contrib/registry/consul/v2(API 兼容) - Go module:yz-svr → git.5iot.com/iotlcm-public/iotlcm-upstream-proxy - 凭据与内部标识脱敏:MySQL/Redis/Consul 凭据、私网与生产 IP、内网域名 - 移除内部资产:CI 触发器 - Dockerfile 改用公共基础镜像,移除写死的 registry 凭据 - 补 Apache-2.0 LICENSE,重写 README,LF 行尾 + .gitattributes --- .gitattributes | 13 + .gitignore | 21 + LICENSE | 201 ++++++ Makefile | 7 + api/cardquery/cardquery.go | 15 + api/cardquery/v1/simbase.go | 15 + api/channel/channel.go | 12 + api/channel/v1/refresh.go | 23 + build/Dockerfile | 34 + build/dev_config.yaml | 118 ++++ config/config.yaml | 112 ++++ go.mod | 72 ++ go.sum | 342 ++++++++++ hack/config.yaml | 19 + hack/hack-cli.mk | 20 + hack/hack.mk | 99 +++ internal/cmd/cmd.go | 98 +++ internal/cmd/http.go | 19 + internal/cmd/http_test.go | 38 ++ internal/consts/consts.go | 6 + internal/controller/cardquery/cardquery.go | 5 + .../cardquery/cardquery_app_diagnosis.go | 79 +++ .../cardquery/cardquery_app_diagnosis_test.go | 55 ++ .../controller/cardquery/cardquery_new.go | 15 + .../cardquery/cardquery_v1_simcard.go | 54 ++ internal/controller/channel/channel.go | 9 + .../controller/channel/channel_v1_refresh.go | 25 + internal/controller/infopb/info.go | 35 + internal/controller/msgcallpb/msgcallpb.go | 47 ++ internal/dao/internal/t_sim_card_info.go | 187 ++++++ .../dao/internal/t_sim_channel_template.go | 101 +++ internal/dao/internal/t_sim_channels.go | 117 ++++ internal/dao/t_sim_card_info.go | 22 + internal/dao/t_sim_channel_template.go | 22 + internal/dao/t_sim_channels.go | 22 + .../logic/channelconfig/channel_config.go | 259 +++++++ .../channelconfig/channel_config_test.go | 37 + .../logic/channelconfig/channel_update.go | 30 + internal/logic/logic.go | 12 + internal/logic/simKeyFill/simKeyFill.go | 177 +++++ internal/logic/upstreamapi/errcode_test.go | 61 ++ .../logic/upstreamapi/normal_gate_test.go | 171 +++++ internal/logic/upstreamapi/upstreamapi.go | 388 +++++++++++ .../logic/upstreamimpl/apika_template_test.go | 24 + internal/logic/upstreamimpl/dx/client.go | 96 +++ internal/logic/upstreamimpl/dx/sign.go | 96 +++ .../logic/upstreamimpl/gateway_response.go | 32 + internal/logic/upstreamimpl/mingqian_test.go | 77 +++ .../mobile_public_template_test.go | 33 + internal/logic/upstreamimpl/sim_dx.go | 95 +++ internal/logic/upstreamimpl/sim_yd.go | 116 ++++ .../upstreamimpl/template_parity_test.go | 78 +++ internal/model/do/t_api.go | 27 + internal/model/do/t_customer.go | 35 + internal/model/do/t_sim_agent_package.go | 34 + internal/model/do/t_sim_agent_packet.go | 29 + internal/model/do/t_sim_card_info.go | 73 ++ internal/model/do/t_sim_channel_template.go | 27 + internal/model/do/t_sim_channels.go | 35 + internal/model/entity/t_api.go | 25 + internal/model/entity/t_customer.go | 33 + internal/model/entity/t_sim_agent_package.go | 32 + internal/model/entity/t_sim_agent_packet.go | 27 + internal/model/entity/t_sim_card_info.go | 71 ++ .../model/entity/t_sim_channel_template.go | 25 + internal/model/entity/t_sim_channels.go | 33 + internal/model/sim_card_channel.go | 43 ++ internal/model/sim_chan_api.go | 36 + internal/packed/packed.go | 1 + internal/pb/common.pb.go | 206 ++++++ internal/pb/info.pb.go | 257 +++++++ internal/pb/info_grpc.pb.go | 122 ++++ internal/pb/msgbus.pb.go | 632 ++++++++++++++++++ internal/pb/msgbus_grpc.pb.go | 155 +++++ internal/pb/msgcall.pb.go | 237 +++++++ internal/pb/msgcall_grpc.pb.go | 155 +++++ internal/pb/notice.pb.go | 197 ++++++ internal/pb/notice_grpc.pb.go | 122 ++++ internal/pub/pkg/initconsul/init_consul.go | 32 + internal/pub/pkg/version/version.go | 51 ++ internal/pub/xjpubconsts/http.go | 14 + internal/pub/xjpubconsts/services.go | 17 + .../pub/xjpublogic/httpclient/httpclient.go | 100 +++ .../httpclient/upstream_proxy_client.go | 37 + internal/pub/xjpublogic/info/info.go | 68 ++ internal/pub/xjpublogic/info/inforpc.go | 127 ++++ .../pub/xjpublogic/middleware/middleware.go | 118 ++++ internal/pub/xjpublogic/xjpublogic.go | 11 + internal/pub/xjpubmodel/info.go | 18 + internal/pub/xjpubmodel/response/common.go | 12 + internal/pub/xjpubmodel/response/response.go | 67 ++ internal/pub/xjpubmodel/sim_chan_api.go | 48 ++ internal/pub/xjpubmodel/xj_metric.go | 91 +++ internal/pub/xjpubservice/httpclient.go | 53 ++ internal/pub/xjpubservice/info.go | 60 ++ internal/pub/xjpubservice/middleware.go | 40 ++ internal/service/channelconfig.go | 55 ++ internal/service/sim_key_fill.go | 33 + internal/service/upstreamapi.go | 44 ++ internal/service/upstreamimpl.go | 49 ++ main.go | 42 ++ manifest/config/config.yaml | 112 ++++ .../deploy/kustomize/base/deployment.yaml | 21 + .../deploy/kustomize/base/kustomization.yaml | 8 + manifest/deploy/kustomize/base/service.yaml | 12 + .../kustomize/overlays/develop/configmap.yaml | 14 + .../overlays/develop/deployment.yaml | 10 + .../overlays/develop/kustomization.yaml | 14 + manifest/docker/Dockerfile | 16 + manifest/docker/docker.sh | 8 + 110 files changed, 7932 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 api/cardquery/cardquery.go create mode 100644 api/cardquery/v1/simbase.go create mode 100644 api/channel/channel.go create mode 100644 api/channel/v1/refresh.go create mode 100644 build/Dockerfile create mode 100644 build/dev_config.yaml create mode 100644 config/config.yaml create mode 100644 go.mod create mode 100644 go.sum create mode 100644 hack/config.yaml create mode 100644 hack/hack-cli.mk create mode 100644 hack/hack.mk create mode 100644 internal/cmd/cmd.go create mode 100644 internal/cmd/http.go create mode 100644 internal/cmd/http_test.go create mode 100644 internal/consts/consts.go create mode 100644 internal/controller/cardquery/cardquery.go create mode 100644 internal/controller/cardquery/cardquery_app_diagnosis.go create mode 100644 internal/controller/cardquery/cardquery_app_diagnosis_test.go create mode 100644 internal/controller/cardquery/cardquery_new.go create mode 100644 internal/controller/cardquery/cardquery_v1_simcard.go create mode 100644 internal/controller/channel/channel.go create mode 100644 internal/controller/channel/channel_v1_refresh.go create mode 100644 internal/controller/infopb/info.go create mode 100644 internal/controller/msgcallpb/msgcallpb.go create mode 100644 internal/dao/internal/t_sim_card_info.go create mode 100644 internal/dao/internal/t_sim_channel_template.go create mode 100644 internal/dao/internal/t_sim_channels.go create mode 100644 internal/dao/t_sim_card_info.go create mode 100644 internal/dao/t_sim_channel_template.go create mode 100644 internal/dao/t_sim_channels.go create mode 100644 internal/logic/channelconfig/channel_config.go create mode 100644 internal/logic/channelconfig/channel_config_test.go create mode 100644 internal/logic/channelconfig/channel_update.go create mode 100644 internal/logic/logic.go create mode 100644 internal/logic/simKeyFill/simKeyFill.go create mode 100644 internal/logic/upstreamapi/errcode_test.go create mode 100644 internal/logic/upstreamapi/normal_gate_test.go create mode 100644 internal/logic/upstreamapi/upstreamapi.go create mode 100644 internal/logic/upstreamimpl/apika_template_test.go create mode 100644 internal/logic/upstreamimpl/dx/client.go create mode 100644 internal/logic/upstreamimpl/dx/sign.go create mode 100644 internal/logic/upstreamimpl/gateway_response.go create mode 100644 internal/logic/upstreamimpl/mingqian_test.go create mode 100644 internal/logic/upstreamimpl/mobile_public_template_test.go create mode 100644 internal/logic/upstreamimpl/sim_dx.go create mode 100644 internal/logic/upstreamimpl/sim_yd.go create mode 100644 internal/logic/upstreamimpl/template_parity_test.go create mode 100644 internal/model/do/t_api.go create mode 100644 internal/model/do/t_customer.go create mode 100644 internal/model/do/t_sim_agent_package.go create mode 100644 internal/model/do/t_sim_agent_packet.go create mode 100644 internal/model/do/t_sim_card_info.go create mode 100644 internal/model/do/t_sim_channel_template.go create mode 100644 internal/model/do/t_sim_channels.go create mode 100644 internal/model/entity/t_api.go create mode 100644 internal/model/entity/t_customer.go create mode 100644 internal/model/entity/t_sim_agent_package.go create mode 100644 internal/model/entity/t_sim_agent_packet.go create mode 100644 internal/model/entity/t_sim_card_info.go create mode 100644 internal/model/entity/t_sim_channel_template.go create mode 100644 internal/model/entity/t_sim_channels.go create mode 100644 internal/model/sim_card_channel.go create mode 100644 internal/model/sim_chan_api.go create mode 100644 internal/packed/packed.go create mode 100644 internal/pb/common.pb.go create mode 100644 internal/pb/info.pb.go create mode 100644 internal/pb/info_grpc.pb.go create mode 100644 internal/pb/msgbus.pb.go create mode 100644 internal/pb/msgbus_grpc.pb.go create mode 100644 internal/pb/msgcall.pb.go create mode 100644 internal/pb/msgcall_grpc.pb.go create mode 100644 internal/pb/notice.pb.go create mode 100644 internal/pb/notice_grpc.pb.go create mode 100644 internal/pub/pkg/initconsul/init_consul.go create mode 100644 internal/pub/pkg/version/version.go create mode 100644 internal/pub/xjpubconsts/http.go create mode 100644 internal/pub/xjpubconsts/services.go create mode 100644 internal/pub/xjpublogic/httpclient/httpclient.go create mode 100644 internal/pub/xjpublogic/httpclient/upstream_proxy_client.go create mode 100644 internal/pub/xjpublogic/info/info.go create mode 100644 internal/pub/xjpublogic/info/inforpc.go create mode 100644 internal/pub/xjpublogic/middleware/middleware.go create mode 100644 internal/pub/xjpublogic/xjpublogic.go create mode 100644 internal/pub/xjpubmodel/info.go create mode 100644 internal/pub/xjpubmodel/response/common.go create mode 100644 internal/pub/xjpubmodel/response/response.go create mode 100644 internal/pub/xjpubmodel/sim_chan_api.go create mode 100644 internal/pub/xjpubmodel/xj_metric.go create mode 100644 internal/pub/xjpubservice/httpclient.go create mode 100644 internal/pub/xjpubservice/info.go create mode 100644 internal/pub/xjpubservice/middleware.go create mode 100644 internal/service/channelconfig.go create mode 100644 internal/service/sim_key_fill.go create mode 100644 internal/service/upstreamapi.go create mode 100644 internal/service/upstreamimpl.go create mode 100644 main.go create mode 100644 manifest/config/config.yaml create mode 100644 manifest/deploy/kustomize/base/deployment.yaml create mode 100644 manifest/deploy/kustomize/base/kustomization.yaml create mode 100644 manifest/deploy/kustomize/base/service.yaml create mode 100644 manifest/deploy/kustomize/overlays/develop/configmap.yaml create mode 100644 manifest/deploy/kustomize/overlays/develop/deployment.yaml create mode 100644 manifest/deploy/kustomize/overlays/develop/kustomization.yaml create mode 100644 manifest/docker/Dockerfile create mode 100644 manifest/docker/docker.sh diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..5837541 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,13 @@ +# 统一使用 LF,避免跨平台协作时产生整文件 diff +* text=auto eol=lf + +# 二进制文件不做行尾转换 +*.png binary +*.jpg binary +*.gif binary +*.ico binary +*.woff binary +*.woff2 binary +*.ttf binary +*.pdf binary +*.exe binary diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4e32e2c --- /dev/null +++ b/.gitignore @@ -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 \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -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. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..2a6e6e9 --- /dev/null +++ b/Makefile @@ -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 \ No newline at end of file diff --git a/api/cardquery/cardquery.go b/api/cardquery/cardquery.go new file mode 100644 index 0000000..7d530d3 --- /dev/null +++ b/api/cardquery/cardquery.go @@ -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) +} diff --git a/api/cardquery/v1/simbase.go b/api/cardquery/v1/simbase.go new file mode 100644 index 0000000..e0cb49b --- /dev/null +++ b/api/cardquery/v1/simbase.go @@ -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 diff --git a/api/channel/channel.go b/api/channel/channel.go new file mode 100644 index 0000000..e3f90ad --- /dev/null +++ b/api/channel/channel.go @@ -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) +} diff --git a/api/channel/v1/refresh.go b/api/channel/v1/refresh.go new file mode 100644 index 0000000..8a51530 --- /dev/null +++ b/api/channel/v1/refresh.go @@ -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"` +} diff --git a/build/Dockerfile b/build/Dockerfile new file mode 100644 index 0000000..96f7950 --- /dev/null +++ b/build/Dockerfile @@ -0,0 +1,34 @@ +FROM golang:1.25-alpine AS builder + +LABEL stage=gobuilder + +ENV GOPROXY=https://goproxy.cn,direct + +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 alpine:3.23 + +RUN apk add --no-cache ca-certificates tzdata + +COPY --from=builder /usr/share/zoneinfo/Asia/Shanghai /usr/share/zoneinfo/Asia/Shanghai +ENV TZ=Asia/Shanghai + +EXPOSE 18510 + +WORKDIR /app +COPY --from=builder /build/iotlcmupstreamproxy_svc /app/iotlcmupstreamproxy_svc +COPY --from=builder /build/build/dev_config.yaml /app/config/config.yaml + +CMD ["./iotlcmupstreamproxy_svc"] diff --git a/build/dev_config.yaml b/build/dev_config.yaml new file mode 100644 index 0000000..3a368a6 --- /dev/null +++ b/build/dev_config.yaml @@ -0,0 +1,118 @@ +# config.yaml 配置文件 +# @Origin 源自内部工程基座 + +# @Origin 源自内网工程库,公开版已移除内部标识 + +# 系统配置 +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: "${REDIS_ADDR}" + +# 日志配置 +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: "mysql:${DB_USER}:${DB_PASSWORD}@tcp(${DB_HOST}:3306)/db_yz_iot?charset=utf8mb4&parseTime=True&loc=Asia%2FShanghai" + 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: "sqlite::@file(:memory:)" + # 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: "${REDIS_SENTINEL_MASTER}" #哨兵模式主节点名称 + address: "${REDIS_ADDR}" + db: 0 + pass: "${REDIS_PASSWORD}" + minIdle: 5 #最小空闲连接数 + maxIdle: 30 #允许闲置的最大连接数 + maxActive: 5000 #最大活跃连接数 + idleTimeout: "60s" #连接最大空闲时间,使用时间字符串例如30s/2m/2d + ClientName: "SystemCache" + queue: + mode: "single" #redis模式,支持:single|cluster|sentinel + sentinelMasterName: "${REDIS_SENTINEL_MASTER}" #哨兵模式主节点名称 + address: "${REDIS_ADDR}" + db: 0 + pass: "${REDIS_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" diff --git a/config/config.yaml b/config/config.yaml new file mode 100644 index 0000000..188b033 --- /dev/null +++ b/config/config.yaml @@ -0,0 +1,112 @@ +# config.yaml 配置文件 +# @Origin 源自内部工程基座 + +# @Origin 源自内网工程库,公开版已移除内部标识 + +# 系统配置 +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: "${REDIS_ADDR}" + # address: "${REDIS_ADDR}" + +# 日志配置 +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: "mysql:${DB_USER}:${DB_PASSWORD}@tcp(${DB_HOST}:3306)/db_iotlcm?charset=utf8mb4&parseTime=True&loc=Asia%2FShanghai" + 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: "sqlite::@file(:memory:)" + # 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: "${REDIS_SENTINEL_MASTER}" #哨兵模式主节点名称 + address: "${REDIS_ADDR}" + db: 0 + pass: "${REDIS_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" diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..faf67ad --- /dev/null +++ b/go.mod @@ -0,0 +1,72 @@ +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/registry/consul/v2 v2.10.3 + 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 + google.golang.org/protobuf v1.36.11 +) + +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/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 + 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 + 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 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..c798b85 --- /dev/null +++ b/go.sum @@ -0,0 +1,342 @@ +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/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/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/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.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/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.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.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.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/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/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/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +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-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/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-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-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-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-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/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.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= +google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= +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= +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= diff --git a/hack/config.yaml b/hack/config.yaml new file mode 100644 index 0000000..89cade4 --- /dev/null +++ b/hack/config.yaml @@ -0,0 +1,19 @@ + +# CLI tool, only in development environment. +# https://goframe.org/docs/cli +gfcli: + gen: + dao: + link: "mysql:${DB_USER}:${DB_PASSWORD}@tcp(${DB_HOST}:3306)/db_iotlcm?charset=utf8mb4&parseTime=True&loc=Asia%2FShanghai" + 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 \ No newline at end of file diff --git a/hack/hack-cli.mk b/hack/hack-cli.mk new file mode 100644 index 0000000..f4e2ad2 --- /dev/null +++ b/hack/hack-cli.mk @@ -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; \ No newline at end of file diff --git a/hack/hack.mk b/hack/hack.mk new file mode 100644 index 0000000..7c5ddd6 --- /dev/null +++ b/hack/hack.mk @@ -0,0 +1,99 @@ +.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.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 + + +mod: + + + +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 ${REGISTRY}/docker_iotlcmupstreamproxy:v0 \ + 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" +dockerpush: \ No newline at end of file diff --git a/internal/cmd/cmd.go b/internal/cmd/cmd.go new file mode 100644 index 0000000..ba50889 --- /dev/null +++ b/internal/cmd/cmd.go @@ -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 + }, + } +) diff --git a/internal/cmd/http.go b/internal/cmd/http.go new file mode 100644 index 0000000..f26a06b --- /dev/null +++ b/internal/cmd/http.go @@ -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) + }) +} diff --git a/internal/cmd/http_test.go b/internal/cmd/http_test.go new file mode 100644 index 0000000..0bf6946 --- /dev/null +++ b/internal/cmd/http_test.go @@ -0,0 +1,38 @@ +package cmd + +import ( + "strings" + "testing" + + "github.com/gogf/gf/v2/net/ghttp" +) + +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()) + } + } +} diff --git a/internal/consts/consts.go b/internal/consts/consts.go new file mode 100644 index 0000000..bc567a3 --- /dev/null +++ b/internal/consts/consts.go @@ -0,0 +1,6 @@ +package consts + +const ( + ApiStatusSuccess = 0 + ApiStatusError = 9999 +) diff --git a/internal/controller/cardquery/cardquery.go b/internal/controller/cardquery/cardquery.go new file mode 100644 index 0000000..a6943c4 --- /dev/null +++ b/internal/controller/cardquery/cardquery.go @@ -0,0 +1,5 @@ +// ================================================================================= +// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish. +// ================================================================================= + +package cardquery diff --git a/internal/controller/cardquery/cardquery_app_diagnosis.go b/internal/controller/cardquery/cardquery_app_diagnosis.go new file mode 100644 index 0000000..2f90579 --- /dev/null +++ b/internal/controller/cardquery/cardquery_app_diagnosis.go @@ -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}) +} diff --git a/internal/controller/cardquery/cardquery_app_diagnosis_test.go b/internal/controller/cardquery/cardquery_app_diagnosis_test.go new file mode 100644 index 0000000..6b7a604 --- /dev/null +++ b/internal/controller/cardquery/cardquery_app_diagnosis_test.go @@ -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) + } + } +} diff --git a/internal/controller/cardquery/cardquery_new.go b/internal/controller/cardquery/cardquery_new.go new file mode 100644 index 0000000..38797be --- /dev/null +++ b/internal/controller/cardquery/cardquery_new.go @@ -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{} +} diff --git a/internal/controller/cardquery/cardquery_v1_simcard.go b/internal/controller/cardquery/cardquery_v1_simcard.go new file mode 100644 index 0000000..f656ec3 --- /dev/null +++ b/internal/controller/cardquery/cardquery_v1_simcard.go @@ -0,0 +1,54 @@ +package cardquery + +import ( + "context" + v1 "git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/api/cardquery/v1" + "git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/service" +) + +// func (c *ControllerV1) SimBase(ctx context.Context, req *v1.SimBaseReq) (res *v1.SimBaseRes, err error) { +// g.Log().Debugf(ctx, "SimBase req %+v", req) +// r := g.RequestFromCtx(ctx) +// if r.Method != "POST" { +// return nil, gerror.New("method not allowed") +// } +// path := r.URL.Path +// if path == "" { +// return nil, gerror.New("path is required") +// } +// // 获取渠道KEY +// for _, simKey := range req.SimKeys { +// if simKey.ChanKey == "" { +// simKey.ChanKey = "channel_003" +// } +// } +// // 获取三码信息 + +// url := "http://iotlcmupstreamproxy-http-svc" + path +// client := g.Client() +// client.SetDiscovery(gsvc.GetRegistry()) +// client.SetTimeout(time.Second * 10) +// postData, err := json.Marshal(req) +// if err != nil { +// return nil, err +// } +// resp, err := client.Post(ctx, url, postData) +// if err != nil { +// return nil, err +// } +// body := resp.ReadAllString() +// var simBaseRes model.SimBaseResJson +// err = json.Unmarshal([]byte(body), &simBaseRes) +// if err != nil { +// return nil, err +// } +// return &simBaseRes, nil +// } + +func (c *ControllerV1) SimBase(ctx context.Context, req *v1.SimBaseReq) (res *v1.SimBaseRes, err error) { + out, err := service.SimKeyFill().SimKeyFill(ctx, &req.SimBaseReqJson) + if err != nil { + return nil, err + } + return &out, nil +} diff --git a/internal/controller/channel/channel.go b/internal/controller/channel/channel.go new file mode 100644 index 0000000..83793c9 --- /dev/null +++ b/internal/controller/channel/channel.go @@ -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{} +} diff --git a/internal/controller/channel/channel_v1_refresh.go b/internal/controller/channel/channel_v1_refresh.go new file mode 100644 index 0000000..139dd93 --- /dev/null +++ b/internal/controller/channel/channel_v1_refresh.go @@ -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 +} diff --git a/internal/controller/infopb/info.go b/internal/controller/infopb/info.go new file mode 100644 index 0000000..facf156 --- /dev/null +++ b/internal/controller/infopb/info.go @@ -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 +} diff --git a/internal/controller/msgcallpb/msgcallpb.go b/internal/controller/msgcallpb/msgcallpb.go new file mode 100644 index 0000000..aafc5f9 --- /dev/null +++ b/internal/controller/msgcallpb/msgcallpb.go @@ -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 +} diff --git a/internal/dao/internal/t_sim_card_info.go b/internal/dao/internal/t_sim_card_info.go new file mode 100644 index 0000000..8b728db --- /dev/null +++ b/internal/dao/internal/t_sim_card_info.go @@ -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) +} diff --git a/internal/dao/internal/t_sim_channel_template.go b/internal/dao/internal/t_sim_channel_template.go new file mode 100644 index 0000000..e4901a9 --- /dev/null +++ b/internal/dao/internal/t_sim_channel_template.go @@ -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) +} diff --git a/internal/dao/internal/t_sim_channels.go b/internal/dao/internal/t_sim_channels.go new file mode 100644 index 0000000..7b8ad0d --- /dev/null +++ b/internal/dao/internal/t_sim_channels.go @@ -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) +} diff --git a/internal/dao/t_sim_card_info.go b/internal/dao/t_sim_card_info.go new file mode 100644 index 0000000..adc0175 --- /dev/null +++ b/internal/dao/t_sim_card_info.go @@ -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. diff --git a/internal/dao/t_sim_channel_template.go b/internal/dao/t_sim_channel_template.go new file mode 100644 index 0000000..eaa6ae2 --- /dev/null +++ b/internal/dao/t_sim_channel_template.go @@ -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. diff --git a/internal/dao/t_sim_channels.go b/internal/dao/t_sim_channels.go new file mode 100644 index 0000000..7099238 --- /dev/null +++ b/internal/dao/t_sim_channels.go @@ -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. diff --git a/internal/logic/channelconfig/channel_config.go b/internal/logic/channelconfig/channel_config.go new file mode 100644 index 0000000..6f568d6 --- /dev/null +++ b/internal/logic/channelconfig/channel_config.go @@ -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" + + "git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/dao" + "sync" + "time" + + "github.com/gogf/gf/v2/crypto/gmd5" + "github.com/gogf/gf/v2/encoding/gjson" + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/frame/g" +) + +// channelConfigRow extends the generated entity without losing SQL NULL values. +type channelConfigRow struct { + entity.TSimChannels + IsNormal *int `json:"isNormal" orm:"is_normal"` +} + +type sChannelConfig struct { + sync.RWMutex + channelCache map[string]*model.SimChannelConfigItemCache + loadVersion uint8 + chgLogMaxId int64 + running bool +} + +func NewChannelConfig() *sChannelConfig { + return &sChannelConfig{ + channelCache: make(map[string]*model.SimChannelConfigItemCache), + } +} + +func init() { + service.RegisterChannelConfig(NewChannelConfig()) +} + +func (s *sChannelConfig) Start(ctx context.Context) error { + err := s.LoadAllChannelConfig(ctx) + if err != nil { + g.Log().Errorf(ctx, "ChannelConfig Start err %+v", err) + return err + } + s.running = true + err = s.Loop(ctx) + if err != nil { + g.Log().Errorf(ctx, "ChannelConfig Start err %+v", err) + return err + } + return nil +} + +func (s *sChannelConfig) Stop(ctx context.Context) error { + s.running = false + return nil +} + +func (s *sChannelConfig) Loop(ctx context.Context) error { + g.Log().Infof(ctx, "ChannelConfig loop begin.") + lastLoadAllChannelConfigTime := int64(0) + go func() { + for s.running { + now := time.Now().Unix() + loadAllInterval := g.Cfg().MustGet(ctx, "channel.load_all_interval", 2*60*60).Int64() + if now-lastLoadAllChannelConfigTime >= loadAllInterval { + lastLoadAllChannelConfigTime = now + s.LoadAllChannelConfig(ctx) + } + time.Sleep(1 * time.Second) + } + }() + g.Log().Infof(ctx, "ChannelConfig loop background goroutine started.") + return nil +} + +func (s *sChannelConfig) LoadAllChannelConfig(ctx context.Context) error { + s.loadVersion++ + const warnRowThreshold = 1000 + + dbSimChannels := make([]*channelConfigRow, 0) + err := dao.TSimChannels.Ctx(ctx). + OrderAsc(dao.TSimChannels.Columns().Id). + Scan(&dbSimChannels) + if err != nil && err != sql.ErrNoRows { + return err + } + if len(dbSimChannels) >= warnRowThreshold { + g.Log().Warningf(ctx, "LoadAllChannelConfig TSimChannels row count %d >= %d, confirm small-table assumption or add pagination", + len(dbSimChannels), warnRowThreshold) + } + + dbSimChannelTemplates := make([]*entity.TSimChannelTemplate, 0) + err = dao.TSimChannelTemplate.Ctx(ctx). + OrderAsc(dao.TSimChannelTemplate.Columns().Id). + Scan(&dbSimChannelTemplates) + if err != nil && err != sql.ErrNoRows { + return err + } + if len(dbSimChannelTemplates) >= warnRowThreshold { + g.Log().Warningf(ctx, "LoadAllChannelConfig TSimChannelTemplate row count %d >= %d, confirm small-table assumption or add pagination", + len(dbSimChannelTemplates), warnRowThreshold) + } + + templateByKey := make(map[string]*entity.TSimChannelTemplate, len(dbSimChannelTemplates)) + for _, tpl := range dbSimChannelTemplates { + if tpl != nil { + templateByKey[tpl.TempKey] = tpl + } + } + + s.Lock() + for _, dbSimChannel := range dbSimChannels { + if dbSimChannel == nil { + continue + } + cache := &model.SimChannelConfigItemCache{ + Channel: &dbSimChannel.TSimChannels, + IsNormal: dbSimChannel.IsNormal, + Version: s.getChanVersion(ctx, dbSimChannel), + } + oldChannelCache, _ := s.channelCache[dbSimChannel.ChanKey] + if cache.CanCreateInstance() { + template, ok := templateByKey[dbSimChannel.TempKey] + if !ok { + g.Log().Warningf(ctx, "LoadAllChannelConfig no template for tempKey=%s chanKey=%s", dbSimChannel.TempKey, dbSimChannel.ChanKey) + continue + } + cache.Template = template + cache.Version += s.getTemplateVersion(ctx, template) + } + + if oldChannelCache != nil && oldChannelCache.Version == cache.Version { + continue + } + s.channelCache[dbSimChannel.ChanKey] = cache + } + s.Unlock() + return nil +} + +func (s *sChannelConfig) RefreshChannel(ctx context.Context, chanKey string) error { + if chanKey == "" { + return gerror.New("chanKey is required") + } + channel := new(channelConfigRow) + if err := dao.TSimChannels.Ctx(ctx).Where(dao.TSimChannels.Columns().ChanKey, chanKey).Scan(channel); err != nil { + return err + } + if channel.ChanKey == "" { + return gerror.Newf("channel not found: %s", chanKey) + } + cache := &model.SimChannelConfigItemCache{ + Channel: &channel.TSimChannels, + IsNormal: channel.IsNormal, + Version: s.getChanVersion(ctx, channel), + } + // Ineligible channels refresh successfully even while their template is incomplete. + if cache.CanCreateInstance() { + template := new(entity.TSimChannelTemplate) + if err := dao.TSimChannelTemplate.Ctx(ctx).Where(dao.TSimChannelTemplate.Columns().TempKey, channel.TempKey).Scan(template); err != nil { + return err + } + if template.TempKey == "" { + return gerror.Newf("channel template not found: %s", channel.TempKey) + } + cache.Template = template + cache.Version += s.getTemplateVersion(ctx, template) + } + s.Lock() + s.channelCache[chanKey] = cache + s.Unlock() + return nil +} + +// RemoveChannel removes a channel configuration cache. Missing keys are +// intentionally treated as success so rename retries remain idempotent. +func (s *sChannelConfig) RemoveChannel(ctx context.Context, chanKey string) error { + chanKey = strings.TrimSpace(chanKey) + if chanKey == "" { + return gerror.New("chanKey is required") + } + s.Lock() + delete(s.channelCache, chanKey) + s.Unlock() + g.Log().Infof(ctx, "channel config removed, chanKey: %s", chanKey) + return nil +} + +// get alarm cache by id +func (s *sChannelConfig) GetChannel(chanKey string) (*model.SimChannelConfigItemCache, bool) { + s.RLock() + defer s.RUnlock() + channelCache, ok := s.channelCache[chanKey] + return channelCache, ok +} + +func (s *sChannelConfig) GetAllChannel(ctx context.Context) ([]*model.SimChannelConfigItemCache, error) { + s.RLock() + defer s.RUnlock() + channels := make([]*model.SimChannelConfigItemCache, 0) + for _, channelCache := range s.channelCache { + channels = append(channels, channelCache) + } + return channels, nil +} + +func (s *sChannelConfig) getChanVersion(ctx context.Context, channel *channelConfigRow) string { + chanKey := channel.ChanKey + + channelNew := &channelConfigRow{ + IsNormal: channel.IsNormal, + TSimChannels: entity.TSimChannels{ + TempKey: channel.TempKey, + ChanKey: channel.ChanKey, + ChanName: channel.ChanName, + ChanConfig: channel.ChanConfig, + ChanStatus: channel.ChanStatus, + ChanAlias: channel.ChanAlias, + RegionCode: channel.RegionCode, + AppKey: channel.AppKey, + AppSecret: channel.AppSecret, + AccessToken: channel.AccessToken, + RefreshToken: channel.RefreshToken, + }, + } + chanVersion, err := gmd5.EncryptString(gjson.MustEncodeString(channelNew)) + if err != nil { + g.Log().Errorf(ctx, "sChannelConfig.getChanVersion encrypt error %+v, chanKey %s", err, chanKey) + return "" + } + g.Log().Debugf(ctx, "sChannelConfig.getChanVersion chanVersion=%s chanKey=%s", chanVersion, chanKey) + return chanVersion +} + +func (s *sChannelConfig) getTemplateVersion(ctx context.Context, template *entity.TSimChannelTemplate) string { + templateKey := template.TempKey + templateNew := &entity.TSimChannelTemplate{ + TempKey: template.TempKey, + TempName: template.TempName, + TempModel: template.TempModel, + TempConfig: template.TempConfig, + TempStatus: template.TempStatus, + } + templateVersion, err := gmd5.EncryptString(gjson.MustEncodeString(templateNew)) + if err != nil { + g.Log().Errorf(ctx, "sChannelConfig.getTemplateVersion encrypt error %+v, templateKey %s, templateNew %+v", err, templateKey, templateNew) + return "" + } + g.Log().Debugf(ctx, "sChannelConfig.getTemplateVersion templateVersion=%s templateKey=%s", templateVersion, templateKey) + return templateVersion +} diff --git a/internal/logic/channelconfig/channel_config_test.go b/internal/logic/channelconfig/channel_config_test.go new file mode 100644 index 0000000..7b40e89 --- /dev/null +++ b/internal/logic/channelconfig/channel_config_test.go @@ -0,0 +1,37 @@ +package channelconfig + +import ( + "context" + "git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/model/entity" + "testing" +) + +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) + } + }) + } +} diff --git a/internal/logic/channelconfig/channel_update.go b/internal/logic/channelconfig/channel_update.go new file mode 100644 index 0000000..116129c --- /dev/null +++ b/internal/logic/channelconfig/channel_update.go @@ -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 +} diff --git a/internal/logic/logic.go b/internal/logic/logic.go new file mode 100644 index 0000000..b9feabf --- /dev/null +++ b/internal/logic/logic.go @@ -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" +) diff --git a/internal/logic/simKeyFill/simKeyFill.go b/internal/logic/simKeyFill/simKeyFill.go new file mode 100644 index 0000000..4ac2cd1 --- /dev/null +++ b/internal/logic/simKeyFill/simKeyFill.go @@ -0,0 +1,177 @@ +package simKeyFill + +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/model/entity" + "git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/service" + "strings" + + "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) { + 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 + } + } +} diff --git a/internal/logic/upstreamapi/errcode_test.go b/internal/logic/upstreamapi/errcode_test.go new file mode 100644 index 0000000..528e9a4 --- /dev/null +++ b/internal/logic/upstreamapi/errcode_test.go @@ -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":"8986112520409968013"},"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: "8986112520409968013", + }}, + }) + 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) + } +} diff --git a/internal/logic/upstreamapi/normal_gate_test.go b/internal/logic/upstreamapi/normal_gate_test.go new file mode 100644 index 0000000..0cf7f56 --- /dev/null +++ b/internal/logic/upstreamapi/normal_gate_test.go @@ -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") + } + }) + } + }) + } +} diff --git a/internal/logic/upstreamapi/upstreamapi.go b/internal/logic/upstreamapi/upstreamapi.go new file mode 100644 index 0000000..0caeb0e --- /dev/null +++ b/internal/logic/upstreamapi/upstreamapi.go @@ -0,0 +1,388 @@ +package upstreamapi + +import ( + "context" + "encoding/json" + "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" + "strings" + "sync" + "time" + + "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) + 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: json.RawMessage(`{"status": "-2"}`), + }) + } + 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: json.RawMessage(`{"status": "-2"}`), + }) + } + 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: json.RawMessage(`{"status": "-2"}`), + }) + } + } 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 + } + } +} diff --git a/internal/logic/upstreamimpl/apika_template_test.go b/internal/logic/upstreamimpl/apika_template_test.go new file mode 100644 index 0000000..19748aa --- /dev/null +++ b/internal/logic/upstreamimpl/apika_template_test.go @@ -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") + } +} diff --git a/internal/logic/upstreamimpl/dx/client.go b/internal/logic/upstreamimpl/dx/client.go new file mode 100644 index 0000000..32e23ec --- /dev/null +++ b/internal/logic/upstreamimpl/dx/client.go @@ -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 +} diff --git a/internal/logic/upstreamimpl/dx/sign.go b/internal/logic/upstreamimpl/dx/sign.go new file mode 100644 index 0000000..940da7a --- /dev/null +++ b/internal/logic/upstreamimpl/dx/sign.go @@ -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 +} diff --git a/internal/logic/upstreamimpl/gateway_response.go b/internal/logic/upstreamimpl/gateway_response.go new file mode 100644 index 0000000..0f2c8e8 --- /dev/null +++ b/internal/logic/upstreamimpl/gateway_response.go @@ -0,0 +1,32 @@ +package upstreamimpl + +import ( + "encoding/json" + "fmt" + "git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/model" + "strings" + + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/net/gclient" +) + +func readGatewayResponse(response *gclient.Response) (model.SimBaseResJson, error) { + defer response.Close() + body := response.ReadAll() + var output struct { + Code *int `json:"code"` + Message string `json:"message"` + Data model.SimBaseResJson `json:"data"` + } + if err := json.Unmarshal(body, &output); err != nil { + return nil, gerror.Wrap(err, "解析网关响应失败") + } + if response.StatusCode < 200 || response.StatusCode >= 300 || output.Code == nil || *output.Code != 0 { + message := strings.TrimSpace(output.Message) + if message == "" { + message = fmt.Sprintf("网关响应无效,HTTP状态码:%d", response.StatusCode) + } + return nil, gerror.New(message) + } + return output.Data, nil +} diff --git a/internal/logic/upstreamimpl/mingqian_test.go b/internal/logic/upstreamimpl/mingqian_test.go new file mode 100644 index 0000000..1297e06 --- /dev/null +++ b/internal/logic/upstreamimpl/mingqian_test.go @@ -0,0 +1,77 @@ +package upstreamimpl_test + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "reflect" + "strings" + "testing" + + _ "git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/logic" + "git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/model" + "git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/model/entity" + "git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/service" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/os/gcfg" +) + +func TestMingqianProxyTemplate(t *testing.T) { + template, ok := service.UpstreamAPI().GetTemp("sim_mingqian_v3") + if !ok || template == nil { + t.Fatal("mingqian proxy template missing") + } + instance := template.NewTemp(context.Background(), &model.SimChannelConfigItemCache{Template: &entity.TSimChannelTemplate{TempKey: "sim_mingqian_v3"}, Channel: &entity.TSimChannels{ChanKey: "mingqian-test"}}) + if instance == nil { + t.Fatal("mingqian proxy instance missing") + } +} + +func TestMingqianProxyPreservesFlowFields(t *testing.T) { + const response = `{"code":0,"data":[{"code":0,"message":"","simKey":{"chanKey":"mingqian-test","iccid":"A"},"resData":{"dataUsage":1,"packageFlow":{"totalFlow":10240,"dataUsage":8192,"remainingFlow":2048}}},{"code":0,"message":"package flow unavailable","simKey":{"chanKey":"mingqian-test","iccid":"B"},"resData":{"dataUsage":2,"packageFlowError":"mingqian: business code 500"}},{"code":1,"message":"monthly usage unavailable","simKey":{"chanKey":"mingqian-test","iccid":"C"},"resData":{"dataUsage":0,"packageFlow":{"totalFlow":10,"dataUsage":8,"remainingFlow":2}}}]}` + server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { + if request.Method != http.MethodPost || request.URL.Path != "/sim/query/sim-datausage" { + t.Errorf("wrong gateway route: %s %s", request.Method, request.URL.Path) + } + var input model.SimBaseReqJson + if err := request.ParseForm(); err != nil { + t.Error(err) + } + if err := json.Unmarshal([]byte(request.Form.Get("simKeys")), &input.SimKeys); err != nil || len(input.SimKeys) != 3 || input.SimKeys[0].ICCID != "A" { + t.Errorf("request changed: %+v %v", input, err) + } + writer.Header().Set("Content-Type", "application/json") + fmt.Fprint(writer, response) + })) + t.Cleanup(server.Close) + config, err := gcfg.NewAdapterContent(fmt.Sprintf(`{"gateway":{"address":%q,"timeout":"1s"}}`, strings.TrimPrefix(server.URL, "http://"))) + if err != nil { + t.Fatal(err) + } + original := g.Cfg().GetAdapter() + g.Cfg().SetAdapter(config) + t.Cleanup(func() { g.Cfg().SetAdapter(original) }) + template, ok := service.UpstreamAPI().GetTemp("sim_mingqian_v3") + if !ok { + t.Fatal("missing Mingqian registration") + } + instance := template.NewTemp(context.Background(), &model.SimChannelConfigItemCache{Template: &entity.TSimChannelTemplate{TempKey: "sim_mingqian_v3"}, Channel: &entity.TSimChannels{ChanKey: "mingqian-test"}}) + request := &ghttp.Request{Request: httptest.NewRequest(http.MethodPost, "/sim/query/sim-datausage", nil)} + output, err := instance.SimBase(request.Context(), &model.SimBaseReqJson{ + SimKeys: []*model.SimKey{{ChanKey: "mingqian-test", ICCID: "A"}, {ChanKey: "mingqian-test", ICCID: "B"}, {ChanKey: "mingqian-test", ICCID: "C"}}, + ReqData: json.RawMessage(`{}`), + }) + if err != nil { + t.Fatal(err) + } + var expected model.SimResponseData[model.SimBaseResJson] + if err := json.Unmarshal([]byte(response), &expected); err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(output, expected.Data) { + t.Fatalf("proxy dropped package fields or changed failure status: %+v", output) + } +} diff --git a/internal/logic/upstreamimpl/mobile_public_template_test.go b/internal/logic/upstreamimpl/mobile_public_template_test.go new file mode 100644 index 0000000..b08eea4 --- /dev/null +++ b/internal/logic/upstreamimpl/mobile_public_template_test.go @@ -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") + } + }) + } +} diff --git a/internal/logic/upstreamimpl/sim_dx.go b/internal/logic/upstreamimpl/sim_dx.go new file mode 100644 index 0000000..e2b9477 --- /dev/null +++ b/internal/logic/upstreamimpl/sim_dx.go @@ -0,0 +1,95 @@ +package upstreamimpl + +import ( + "context" + "fmt" + "git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/logic/upstreamimpl/dx" + "git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/model" + "git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/service" + "regexp" + "time" + + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/gsvc" +) + +// XML命名空间正则 +var xmlNamespaceRegex = regexp.MustCompile(`xmlns[^=]*="[^"]*"\s*|:web:`) + +type sSimDX struct { + channelConfig *model.SimChannelConfigItemCache + client *dx.Client +} + +func NewSimDX() *sSimDX { + return &sSimDX{} +} + +func init() { + s := NewSimDX() + service.UpstreamAPI().RegisterTemp("sim_dx_v1", s) + service.UpstreamAPI().RegisterTemp("sim_dx_v3", s) + service.RegisterSimDX(s) +} + +func (s *sSimDX) NewTemp(ctx context.Context, channelConfig *model.SimChannelConfigItemCache) model.SimCardChannelI { + client := dx.NewClient(dx.Config{ + BaseURL: dx.DefaultBaseURL, + AppKey: channelConfig.Channel.AppKey, + AppSecret: channelConfig.Channel.AppSecret, + }) + return &sSimDX{ + channelConfig: channelConfig, + client: client, + } +} + +func (s *sSimDX) getUpstreamUrl(ctx context.Context, serviceUrl string) (string, error) { + address, err := g.Cfg().Get(ctx, "gateway.address") + if err != nil { + return "", err + } + return fmt.Sprintf("http://%s%s", address, serviceUrl), nil + // return fmt.Sprintf("http://%s%s", "127.0.0.1:8110", serviceUrl), nil + +} + +func (s *sSimDX) getUpstreamTimeout(ctx context.Context) time.Duration { + timeout, _ := g.Cfg().Get(ctx, "gateway.timeout") + if timeout == nil { + return 10 * time.Second + } + if d := timeout.Duration(); d > 0 { + return d + } + if seconds := timeout.Int(); seconds > 0 { + return time.Duration(seconds) * time.Second + } + return 10 * time.Second +} + +func (s *sSimDX) SimBase(ctx context.Context, input *model.SimBaseReqJson) (model.SimBaseResJson, error) { + r := g.RequestFromCtx(ctx) + if r.Method != "POST" { + return nil, gerror.New("method not allowed") + } + path := r.URL.Path + if path == "" { + return nil, gerror.New("path is required") + } + url, err := s.getUpstreamUrl(ctx, path) + if err != nil { + g.Log().Debugf(ctx, "SimBase getUpstreamUrl error %+v, ", err) + return nil, err + } + client := g.Client() + client.SetDiscovery(gsvc.GetRegistry()) + client.SetTimeout(s.getUpstreamTimeout(ctx) * time.Duration(len(input.SimKeys))) + resp, err := client.Post(ctx, url, input) + if err != nil { + g.Log().Errorf(ctx, "SimBase post error %+v,url %s, params %+v, ", err, url, input) + return nil, err + } + return readGatewayResponse(resp) +} diff --git a/internal/logic/upstreamimpl/sim_yd.go b/internal/logic/upstreamimpl/sim_yd.go new file mode 100644 index 0000000..c4a3610 --- /dev/null +++ b/internal/logic/upstreamimpl/sim_yd.go @@ -0,0 +1,116 @@ +package upstreamimpl + +import ( + "context" + "fmt" + "git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/model" + "git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/service" + "sync/atomic" + "time" + + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/gsvc" + "golang.org/x/sync/singleflight" +) + +const ( + gUrlBase = "https://api.iot.10086.cn/v5/" +) + +type sSimYD struct { + channelConfig *model.SimChannelConfigItemCache + seqId atomic.Int64 + tokenFlight singleflight.Group +} + +func NewSimYD() *sSimYD { + return &sSimYD{} +} + +func init() { + s := NewSimYD() + service.UpstreamAPI().RegisterTemp("sim_yd_v5", s) + service.UpstreamAPI().RegisterTemp("sim_yd_mock_v5", s) + // These channel implementations live in the upstream gateway. The proxy + // forwards their API calls through the same generic gateway route, so any + // template registered in iotlcm-upstream-chan must be registered here too: + // channel refresh fails with "channel template not registered" otherwise. + // sSimYD has no template-specific logic, so an alias per template suffices. + service.UpstreamAPI().RegisterTemp("sim_sdtywl_v1", s) + service.UpstreamAPI().RegisterTemp("sim_onelink_tengyu_v1", s) + service.UpstreamAPI().RegisterTemp("sim_onelink_xuzhou_mobile_v1", s) + service.UpstreamAPI().RegisterTemp("sim_onelink_lelian_v1", s) + service.UpstreamAPI().RegisterTemp("sim_onelink_qianxun_v1", s) + service.UpstreamAPI().RegisterTemp("sim_onelink_henan_xinjing_v1", s) + service.UpstreamAPI().RegisterTemp("sim_henan_xinjing_direct_v1", s) + service.UpstreamAPI().RegisterTemp("sim_onelink_zibo_v1", s) + service.UpstreamAPI().RegisterTemp("sim_tibiot_v1", s) + service.UpstreamAPI().RegisterTemp("sim_qianxun_v3", s) + service.UpstreamAPI().RegisterTemp("sim_yunda_v1", s) + service.UpstreamAPI().RegisterTemp("sim_mingqian_v3", s) + service.UpstreamAPI().RegisterTemp("sim_daihe_v1", s) + service.UpstreamAPI().RegisterTemp("sim_onelink_daihe_v1", s) + service.UpstreamAPI().RegisterTemp("sim_onelink_daliuliang_v1", s) + service.UpstreamAPI().RegisterTemp("sim__daliuliang_v1", s) + service.UpstreamAPI().RegisterTemp("sim_onelink_mobile_public_v1", s) + service.UpstreamAPI().RegisterTemp("sim_onelink_kedang_v1", s) + service.UpstreamAPI().RegisterTemp("lanshang_8003", s) + service.UpstreamAPI().RegisterTemp("lanshang_8000", s) + service.UpstreamAPI().RegisterTemp("sim_onelink_yunze_v1", s) + // 江苏倍信(jsbxkj)共享 Token。协议实现在 chan 的 beixin_token_provider.go; + // proxy 只负责把业务 API 转发到 gateway,与腾宇/澜数同一形态。 + service.UpstreamAPI().RegisterTemp("sim_onelink_beixin_v1", s) + service.UpstreamAPI().RegisterTemp("sim_onelink_lanshu_v1", s) + service.RegisterSimYD(s) +} + +func (s *sSimYD) NewTemp(ctx context.Context, channelConfig *model.SimChannelConfigItemCache) model.SimCardChannelI { + return &sSimYD{ + channelConfig: channelConfig, + } +} + +func (s *sSimYD) getUpstreamUrl(ctx context.Context, serviceUrl string) string { + address, _ := g.Cfg().Get(ctx, "gateway.address") + return fmt.Sprintf("http://%s%s", address, serviceUrl) + // return fmt.Sprintf("http://%s%s", "127.0.0.1:8110", serviceUrl) +} + +func (s *sSimYD) getUpstreamTimeout(ctx context.Context) time.Duration { + timeout, _ := g.Cfg().Get(ctx, "gateway.timeout") + if timeout == nil { + return 10 * time.Second + } + if d := timeout.Duration(); d > 0 { + return d + } + if seconds := timeout.Int(); seconds > 0 { + return time.Duration(seconds) * time.Second + } + return 10 * time.Second +} + +func (s *sSimYD) SimBase(ctx context.Context, input *model.SimBaseReqJson) (model.SimBaseResJson, error) { + r := g.RequestFromCtx(ctx) + if r.Method != "POST" { + g.Log().Errorf(ctx, "SimYD.SimBase, method not allowed, method %s, input %+v", r.Method, input) + return nil, gerror.New("方法调用错误") + } + path := r.URL.Path + if path == "" { + g.Log().Errorf(ctx, "SimYD.SimBase, path is required, input %+v", input) + return nil, gerror.New("路径不能为空") + } + url := s.getUpstreamUrl(ctx, path) + g.Log().Debugf(ctx, "SimYD.SimBase, url %s", url) + client := g.Client() + client.SetDiscovery(gsvc.GetRegistry()) + client.SetTimeout(s.getUpstreamTimeout(ctx) * time.Duration(len(input.SimKeys))) + resp, err := client.Post(ctx, url, input) + if err != nil { + g.Log().Errorf(ctx, "SimYD.SimBase, post error %+v,url %s, params %+v, ", err, url, input) + return nil, gerror.New("请求失败") + } + return readGatewayResponse(resp) +} diff --git a/internal/logic/upstreamimpl/template_parity_test.go b/internal/logic/upstreamimpl/template_parity_test.go new file mode 100644 index 0000000..60db71f --- /dev/null +++ b/internal/logic/upstreamimpl/template_parity_test.go @@ -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) + } + }) + } +} diff --git a/internal/model/do/t_api.go b/internal/model/do/t_api.go new file mode 100644 index 0000000..6333170 --- /dev/null +++ b/internal/model/do/t_api.go @@ -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 // 更新时间 +} diff --git a/internal/model/do/t_customer.go b/internal/model/do/t_customer.go new file mode 100644 index 0000000..6e0d793 --- /dev/null +++ b/internal/model/do/t_customer.go @@ -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 // 更新时间 +} diff --git a/internal/model/do/t_sim_agent_package.go b/internal/model/do/t_sim_agent_package.go new file mode 100644 index 0000000..72a1905 --- /dev/null +++ b/internal/model/do/t_sim_agent_package.go @@ -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 // 更新时间 +} diff --git a/internal/model/do/t_sim_agent_packet.go b/internal/model/do/t_sim_agent_packet.go new file mode 100644 index 0000000..e51fd86 --- /dev/null +++ b/internal/model/do/t_sim_agent_packet.go @@ -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 // 更新时间 +} diff --git a/internal/model/do/t_sim_card_info.go b/internal/model/do/t_sim_card_info.go new file mode 100644 index 0000000..f16e207 --- /dev/null +++ b/internal/model/do/t_sim_card_info.go @@ -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 // 更新时间 +} diff --git a/internal/model/do/t_sim_channel_template.go b/internal/model/do/t_sim_channel_template.go new file mode 100644 index 0000000..a2ea2cd --- /dev/null +++ b/internal/model/do/t_sim_channel_template.go @@ -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 // 更新时间 +} diff --git a/internal/model/do/t_sim_channels.go b/internal/model/do/t_sim_channels.go new file mode 100644 index 0000000..d3a3ab8 --- /dev/null +++ b/internal/model/do/t_sim_channels.go @@ -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 // 更新时间 +} diff --git a/internal/model/entity/t_api.go b/internal/model/entity/t_api.go new file mode 100644 index 0000000..e5cc558 --- /dev/null +++ b/internal/model/entity/t_api.go @@ -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:"更新时间"` +} diff --git a/internal/model/entity/t_customer.go b/internal/model/entity/t_customer.go new file mode 100644 index 0000000..f0a7703 --- /dev/null +++ b/internal/model/entity/t_customer.go @@ -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:"更新时间"` +} diff --git a/internal/model/entity/t_sim_agent_package.go b/internal/model/entity/t_sim_agent_package.go new file mode 100644 index 0000000..e4111d9 --- /dev/null +++ b/internal/model/entity/t_sim_agent_package.go @@ -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:"更新时间"` +} diff --git a/internal/model/entity/t_sim_agent_packet.go b/internal/model/entity/t_sim_agent_packet.go new file mode 100644 index 0000000..d0fe224 --- /dev/null +++ b/internal/model/entity/t_sim_agent_packet.go @@ -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:"更新时间"` +} diff --git a/internal/model/entity/t_sim_card_info.go b/internal/model/entity/t_sim_card_info.go new file mode 100644 index 0000000..6e1e371 --- /dev/null +++ b/internal/model/entity/t_sim_card_info.go @@ -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:"更新时间"` +} diff --git a/internal/model/entity/t_sim_channel_template.go b/internal/model/entity/t_sim_channel_template.go new file mode 100644 index 0000000..6dcd0d0 --- /dev/null +++ b/internal/model/entity/t_sim_channel_template.go @@ -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:"更新时间"` +} diff --git a/internal/model/entity/t_sim_channels.go b/internal/model/entity/t_sim_channels.go new file mode 100644 index 0000000..41cf4bc --- /dev/null +++ b/internal/model/entity/t_sim_channels.go @@ -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:"更新时间"` +} diff --git a/internal/model/sim_card_channel.go b/internal/model/sim_card_channel.go new file mode 100644 index 0000000..3f21539 --- /dev/null +++ b/internal/model/sim_card_channel.go @@ -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 +} diff --git a/internal/model/sim_chan_api.go b/internal/model/sim_chan_api.go new file mode 100644 index 0000000..810a033 --- /dev/null +++ b/internal/model/sim_chan_api.go @@ -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] diff --git a/internal/packed/packed.go b/internal/packed/packed.go new file mode 100644 index 0000000..e20ab1e --- /dev/null +++ b/internal/packed/packed.go @@ -0,0 +1 @@ +package packed diff --git a/internal/pb/common.pb.go b/internal/pb/common.pb.go new file mode 100644 index 0000000..9a5c740 --- /dev/null +++ b/internal/pb/common.pb.go @@ -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 +} diff --git a/internal/pb/info.pb.go b/internal/pb/info.pb.go new file mode 100644 index 0000000..271cd30 --- /dev/null +++ b/internal/pb/info.pb.go @@ -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 +} diff --git a/internal/pb/info_grpc.pb.go b/internal/pb/info_grpc.pb.go new file mode 100644 index 0000000..716c3aa --- /dev/null +++ b/internal/pb/info_grpc.pb.go @@ -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", +} diff --git a/internal/pb/msgbus.pb.go b/internal/pb/msgbus.pb.go new file mode 100644 index 0000000..e215821 --- /dev/null +++ b/internal/pb/msgbus.pb.go @@ -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 +} diff --git a/internal/pb/msgbus_grpc.pb.go b/internal/pb/msgbus_grpc.pb.go new file mode 100644 index 0000000..8ea58fb --- /dev/null +++ b/internal/pb/msgbus_grpc.pb.go @@ -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", +} diff --git a/internal/pb/msgcall.pb.go b/internal/pb/msgcall.pb.go new file mode 100644 index 0000000..6f97ea5 --- /dev/null +++ b/internal/pb/msgcall.pb.go @@ -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 +} diff --git a/internal/pb/msgcall_grpc.pb.go b/internal/pb/msgcall_grpc.pb.go new file mode 100644 index 0000000..a0bcd96 --- /dev/null +++ b/internal/pb/msgcall_grpc.pb.go @@ -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", +} diff --git a/internal/pb/notice.pb.go b/internal/pb/notice.pb.go new file mode 100644 index 0000000..42dd281 --- /dev/null +++ b/internal/pb/notice.pb.go @@ -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 +} diff --git a/internal/pb/notice_grpc.pb.go b/internal/pb/notice_grpc.pb.go new file mode 100644 index 0000000..42ff435 --- /dev/null +++ b/internal/pb/notice_grpc.pb.go @@ -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 ¬iceClient{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", +} diff --git a/internal/pub/pkg/initconsul/init_consul.go b/internal/pub/pkg/initconsul/init_consul.go new file mode 100644 index 0000000..facf871 --- /dev/null +++ b/internal/pub/pkg/initconsul/init_consul.go @@ -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 +} diff --git a/internal/pub/pkg/version/version.go b/internal/pub/pkg/version/version.go new file mode 100644 index 0000000..9951863 --- /dev/null +++ b/internal/pub/pkg/version/version.go @@ -0,0 +1,51 @@ +// package version +// @Link https://github.com/xiujiecn/xiu-admin +// @Copyright Copyright (c) 2025 LiXiujie +// @Author Lxj +// @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() +} diff --git a/internal/pub/xjpubconsts/http.go b/internal/pub/xjpubconsts/http.go new file mode 100644 index 0000000..8d1ddac --- /dev/null +++ b/internal/pub/xjpubconsts/http.go @@ -0,0 +1,14 @@ +// Package xjpubconsts +// @Link https://github.com/xiujiecn/xiu-admin +// @Copyright Copyright (c) 2025 LiXiujie +// @Author Lxj +// @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" +) diff --git a/internal/pub/xjpubconsts/services.go b/internal/pub/xjpubconsts/services.go new file mode 100644 index 0000000..7089b38 --- /dev/null +++ b/internal/pub/xjpubconsts/services.go @@ -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 服务 +) diff --git a/internal/pub/xjpublogic/httpclient/httpclient.go b/internal/pub/xjpublogic/httpclient/httpclient.go new file mode 100644 index 0000000..a61fae5 --- /dev/null +++ b/internal/pub/xjpublogic/httpclient/httpclient.go @@ -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 +} diff --git a/internal/pub/xjpublogic/httpclient/upstream_proxy_client.go b/internal/pub/xjpublogic/httpclient/upstream_proxy_client.go new file mode 100644 index 0000000..ec2a909 --- /dev/null +++ b/internal/pub/xjpublogic/httpclient/upstream_proxy_client.go @@ -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 +} diff --git a/internal/pub/xjpublogic/info/info.go b/internal/pub/xjpublogic/info/info.go new file mode 100644 index 0000000..fc9c6c0 --- /dev/null +++ b/internal/pub/xjpublogic/info/info.go @@ -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] +} diff --git a/internal/pub/xjpublogic/info/inforpc.go b/internal/pub/xjpublogic/info/inforpc.go new file mode 100644 index 0000000..22bf804 --- /dev/null +++ b/internal/pub/xjpublogic/info/inforpc.go @@ -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] +} diff --git a/internal/pub/xjpublogic/middleware/middleware.go b/internal/pub/xjpublogic/middleware/middleware.go new file mode 100644 index 0000000..a7ebef1 --- /dev/null +++ b/internal/pub/xjpublogic/middleware/middleware.go @@ -0,0 +1,118 @@ +// package middleware +// @Link https://github.com/xiujiecn/xiu-admin +// @Copyright Copyright (c) 2025 LiXiujie +// @Author Lxj +// @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) + } +} diff --git a/internal/pub/xjpublogic/xjpublogic.go b/internal/pub/xjpublogic/xjpublogic.go new file mode 100644 index 0000000..7ed64c2 --- /dev/null +++ b/internal/pub/xjpublogic/xjpublogic.go @@ -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" +) diff --git a/internal/pub/xjpubmodel/info.go b/internal/pub/xjpubmodel/info.go new file mode 100644 index 0000000..1e8486a --- /dev/null +++ b/internal/pub/xjpubmodel/info.go @@ -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{}), + } +} diff --git a/internal/pub/xjpubmodel/response/common.go b/internal/pub/xjpubmodel/response/common.go new file mode 100644 index 0000000..9f9000a --- /dev/null +++ b/internal/pub/xjpubmodel/response/common.go @@ -0,0 +1,12 @@ +// package response +// @Link https://github.com/xiujiecn/xiu-admin +// @Copyright Copyright (c) 2025 LiXiujie +// @Author Lxj +// @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"` +} diff --git a/internal/pub/xjpubmodel/response/response.go b/internal/pub/xjpubmodel/response/response.go new file mode 100644 index 0000000..5f38047 --- /dev/null +++ b/internal/pub/xjpubmodel/response/response.go @@ -0,0 +1,67 @@ +// package response +// @Link https://github.com/xiujiecn/xiu-admin +// @Copyright Copyright (c) 2025 LiXiujie +// @Author Lxj +// @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) +} diff --git a/internal/pub/xjpubmodel/sim_chan_api.go b/internal/pub/xjpubmodel/sim_chan_api.go new file mode 100644 index 0000000..23d8e12 --- /dev/null +++ b/internal/pub/xjpubmodel/sim_chan_api.go @@ -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消息"` +} diff --git a/internal/pub/xjpubmodel/xj_metric.go b/internal/pub/xjpubmodel/xj_metric.go new file mode 100644 index 0000000..e352b0c --- /dev/null +++ b/internal/pub/xjpubmodel/xj_metric.go @@ -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"` // 上报间隔 +} diff --git a/internal/pub/xjpubservice/httpclient.go b/internal/pub/xjpubservice/httpclient.go new file mode 100644 index 0000000..5e7220f --- /dev/null +++ b/internal/pub/xjpubservice/httpclient.go @@ -0,0 +1,53 @@ +// ================================================================================ +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// You can delete these comments if you wish manually maintain this interface file. +// ================================================================================ + +package xjpubservice + +import ( + "context" + + xjpubmodel "git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/pub/xjpubmodel" +) + +type ( + IHttpClient interface { + PostObj(ctx context.Context, serviceName string, path string, req interface{}, res interface{}) (err error) + Post(ctx context.Context, serviceName string, path string, req []byte) (res []byte, err error) + AllServerPost(ctx context.Context, serviceName string, path string, req []byte) (resMap map[string][]byte, err error) + } + IUpstreamProxyClient interface { + // 物联网卡在线信息实时查询 + QuerySimSession(ctx context.Context, req *xjpubmodel.SimSessionInput) (res *xjpubmodel.SimSessionOutput, err error) + // 物联网卡卡状态查询 + QuerySimCardStatus(ctx context.Context, req *xjpubmodel.SimCardStatusInput) (res *xjpubmodel.SimCardStatusOutput, err error) + } +) + +var ( + localHttpClient IHttpClient + localUpstreamProxyClient IUpstreamProxyClient +) + +func HttpClient() IHttpClient { + if localHttpClient == nil { + panic("implement not found for interface IHttpClient, forgot register?") + } + return localHttpClient +} + +func RegisterHttpClient(i IHttpClient) { + localHttpClient = i +} + +func UpstreamProxyClient() IUpstreamProxyClient { + if localUpstreamProxyClient == nil { + panic("implement not found for interface IUpstreamProxyClient, forgot register?") + } + return localUpstreamProxyClient +} + +func RegisterUpstreamProxyClient(i IUpstreamProxyClient) { + localUpstreamProxyClient = i +} diff --git a/internal/pub/xjpubservice/info.go b/internal/pub/xjpubservice/info.go new file mode 100644 index 0000000..33bfaf5 --- /dev/null +++ b/internal/pub/xjpubservice/info.go @@ -0,0 +1,60 @@ +// ================================================================================ +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// You can delete these comments if you wish manually maintain this interface file. +// ================================================================================ + +package xjpubservice + +import ( + "git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/pub/xjpubmodel" +) + +type ( + IInfo interface { + GetInfo() *xjpubmodel.InfoModel + SetInfoName(Type string, Name string, Title string, Description string) + SetInfoBuild(buildVersion string, buildTime string, commitID string) + SetInfoData(data map[string]interface{}) + GetInfoData() map[string]interface{} + AddInfoData(key string, value interface{}) + DelInfoData(key string) + GetInfoDataByKey(key string) interface{} + } + IInfoRpc interface { + GetInfo() *xjpubmodel.InfoModel + SetInfoName(Type string, Name string, Title string, Description string) + SetInfoBuild(buildVersion string, buildTime string, commitID string) + SetInfoData(data map[string]interface{}) + GetInfoData() map[string]interface{} + AddInfoData(key string, value interface{}) + DelInfoData(key string) + GetInfoDataByKey(key string) interface{} + } +) + +var ( + localInfo IInfo + localInfoRpc IInfoRpc +) + +func Info() IInfo { + if localInfo == nil { + panic("implement not found for interface IInfo, forgot register?") + } + return localInfo +} + +func RegisterInfo(i IInfo) { + localInfo = i +} + +func InfoRpc() IInfoRpc { + if localInfoRpc == nil { + panic("implement not found for interface IInfoRpc, forgot register?") + } + return localInfoRpc +} + +func RegisterInfoRpc(i IInfoRpc) { + localInfoRpc = i +} diff --git a/internal/pub/xjpubservice/middleware.go b/internal/pub/xjpubservice/middleware.go new file mode 100644 index 0000000..8d60b7a --- /dev/null +++ b/internal/pub/xjpubservice/middleware.go @@ -0,0 +1,40 @@ +// ================================================================================ +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// You can delete these comments if you wish manually maintain this interface file. +// ================================================================================ + +package xjpubservice + +import ( + "context" + + "github.com/gogf/gf/v2/net/ghttp" +) + +type ( + IMiddleware interface { + // 响应处理中间件 + ResponseHandler(r *ghttp.Request) + // CORS 跨域处理 + CORS(r *ghttp.Request) + // IsExceptLogin 判断是否需要验证登录 + IsExceptLogin(ctx context.Context, path string) bool + // ResponseHandlerRaw 返回处理中间件 + ResponseHandlerRaw(r *ghttp.Request) + } +) + +var ( + localMiddleware IMiddleware +) + +func Middleware() IMiddleware { + if localMiddleware == nil { + panic("implement not found for interface IMiddleware, forgot register?") + } + return localMiddleware +} + +func RegisterMiddleware(i IMiddleware) { + localMiddleware = i +} diff --git a/internal/service/channelconfig.go b/internal/service/channelconfig.go new file mode 100644 index 0000000..a10c399 --- /dev/null +++ b/internal/service/channelconfig.go @@ -0,0 +1,55 @@ +// ================================================================================ +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// You can delete these comments if you wish manually maintain this interface file. +// ================================================================================ + +package service + +import ( + "context" + "git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/model" +) + +type ( + IChannelConfig interface { + Start(ctx context.Context) error + Stop(ctx context.Context) error + Loop(ctx context.Context) error + LoadAllChannelConfig(ctx context.Context) error + RefreshChannel(ctx context.Context, chanKey string) error + RemoveChannel(ctx context.Context, chanKey string) error + // get alarm cache by id + GetChannel(chanKey string) (*model.SimChannelConfigItemCache, bool) + GetAllChannel(ctx context.Context) ([]*model.SimChannelConfigItemCache, error) + } + IChannelUpdate interface { + UpdateAccessToken(ctx context.Context, input *model.UpdateAccessTokenInput) error + } +) + +var ( + localChannelConfig IChannelConfig + localChannelUpdate IChannelUpdate +) + +func ChannelConfig() IChannelConfig { + if localChannelConfig == nil { + panic("implement not found for interface IChannelConfig, forgot register?") + } + return localChannelConfig +} + +func RegisterChannelConfig(i IChannelConfig) { + localChannelConfig = i +} + +func ChannelUpdate() IChannelUpdate { + if localChannelUpdate == nil { + panic("implement not found for interface IChannelUpdate, forgot register?") + } + return localChannelUpdate +} + +func RegisterChannelUpdate(i IChannelUpdate) { + localChannelUpdate = i +} diff --git a/internal/service/sim_key_fill.go b/internal/service/sim_key_fill.go new file mode 100644 index 0000000..28cef63 --- /dev/null +++ b/internal/service/sim_key_fill.go @@ -0,0 +1,33 @@ +// ================================================================================ +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// You can delete these comments if you wish manually maintain this interface file. +// ================================================================================ + +package service + +import ( + "context" + "git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/model" +) + +type ( + ISimKeyFill interface { + SimKeyFill(ctx context.Context, input *model.SimBaseReqJson) (output model.SimBaseResJson, err error) + SetRedis(ctx context.Context, simInfoMap map[string]*model.SimKey) + } +) + +var ( + localSimKeyFill ISimKeyFill +) + +func SimKeyFill() ISimKeyFill { + if localSimKeyFill == nil { + panic("implement not found for interface ISimKeyFill, forgot register?") + } + return localSimKeyFill +} + +func RegisterSimKeyFill(i ISimKeyFill) { + localSimKeyFill = i +} diff --git a/internal/service/upstreamapi.go b/internal/service/upstreamapi.go new file mode 100644 index 0000000..7449c52 --- /dev/null +++ b/internal/service/upstreamapi.go @@ -0,0 +1,44 @@ +// ================================================================================ +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// You can delete these comments if you wish manually maintain this interface file. +// ================================================================================ + +package service + +import ( + "context" + "git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/model" + "git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/model/entity" +) + +type ( + IUpstreamAPI interface { + Start(ctx context.Context) error + Stop(ctx context.Context) error + RefreshChannel(ctx context.Context, chanKey string) error + RemoveChannel(ctx context.Context, chanKey string) error + RegisterChan(chanKey string, version string, channel *model.SimCardChannelCache) + RegisterTemp(tempKey string, channel model.SimCardTempI) + GetTemp(tempKey string) (model.SimCardTempI, bool) + GetChan(chanKey string) (*model.SimCardChannelCache, bool) + // 物联网卡 + SimBase(ctx context.Context, input *model.SimBaseReqJson) (model.SimBaseResJson, error) + // 异步写入redis + SetRedis(ctx context.Context, simCardInfos []*entity.TSimCardInfo) + } +) + +var ( + localUpstreamAPI IUpstreamAPI +) + +func UpstreamAPI() IUpstreamAPI { + if localUpstreamAPI == nil { + panic("implement not found for interface IUpstreamAPI, forgot register?") + } + return localUpstreamAPI +} + +func RegisterUpstreamAPI(i IUpstreamAPI) { + localUpstreamAPI = i +} diff --git a/internal/service/upstreamimpl.go b/internal/service/upstreamimpl.go new file mode 100644 index 0000000..1c66fff --- /dev/null +++ b/internal/service/upstreamimpl.go @@ -0,0 +1,49 @@ +// ================================================================================ +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// You can delete these comments if you wish manually maintain this interface file. +// ================================================================================ + +package service + +import ( + "context" + "git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/model" +) + +type ( + ISimDX interface { + NewTemp(ctx context.Context, channelConfig *model.SimChannelConfigItemCache) model.SimCardChannelI + SimBase(ctx context.Context, input *model.SimBaseReqJson) (model.SimBaseResJson, error) + } + ISimYD interface { + NewTemp(ctx context.Context, channelConfig *model.SimChannelConfigItemCache) model.SimCardChannelI + SimBase(ctx context.Context, input *model.SimBaseReqJson) (model.SimBaseResJson, error) + } +) + +var ( + localSimDX ISimDX + localSimYD ISimYD +) + +func SimDX() ISimDX { + if localSimDX == nil { + panic("implement not found for interface ISimDX, forgot register?") + } + return localSimDX +} + +func RegisterSimDX(i ISimDX) { + localSimDX = i +} + +func SimYD() ISimYD { + if localSimYD == nil { + panic("implement not found for interface ISimYD, forgot register?") + } + return localSimYD +} + +func RegisterSimYD(i ISimYD) { + localSimYD = i +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..d8370b7 --- /dev/null +++ b/main.go @@ -0,0 +1,42 @@ +package main + +import ( + _ "git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/packed" + "os" + + _ "git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/pub/xjpublogic" + _ "github.com/gogf/gf/contrib/drivers/mysql/v2" + _ "github.com/gogf/gf/contrib/drivers/sqlite/v2" + _ "github.com/gogf/gf/contrib/nosql/redis/v2" + + _ "git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/logic" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" + + "git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/cmd" + + "git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/pub/pkg/initconsul" + "git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/pub/pkg/version" + "git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/pub/xjpubservice" +) + +var ( + BuildVersion = "0.0" + BuildTime = "" + CommitID = "" +) + +func main() { + ctx := gctx.GetInitCtx() + // 初始化consul + err := initconsul.InitConsul(ctx) + if err != nil { + g.Log().Fatalf(ctx, "consul初始化失败: %+v", err) + os.Exit(1) + } + // 初始化系统 + version.ShowLogo(ctx, BuildVersion, BuildTime, CommitID) + xjpubservice.Info().SetInfoBuild(BuildVersion, BuildTime, CommitID) + cmd.Main.Run(gctx.GetInitCtx()) +} diff --git a/manifest/config/config.yaml b/manifest/config/config.yaml new file mode 100644 index 0000000..d1a10a8 --- /dev/null +++ b/manifest/config/config.yaml @@ -0,0 +1,112 @@ +# config.yaml 配置文件 +# @Link https://github.com/xiujiecn/xiu-admin +# @Copyright Copyright (c) 2025 LiXiujie +# @Author Lxj +# @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: "localhost:8500" + # address: "192.0.2.70: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: "mysql:${DB_USER}:${DB_PASSWORD}@tcp(${DB_HOST}:3306)/db_iotlcm?charset=utf8mb4&parseTime=True&loc=Asia%2FShanghai" + 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: "sqlite::@file(:memory:)" + # 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: "${REDIS_SENTINEL_MASTER}" #哨兵模式主节点名称 + address: 192.0.2.29:6379 + db: 0 + pass: "${REDIS_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" diff --git a/manifest/deploy/kustomize/base/deployment.yaml b/manifest/deploy/kustomize/base/deployment.yaml new file mode 100644 index 0000000..28f1d69 --- /dev/null +++ b/manifest/deploy/kustomize/base/deployment.yaml @@ -0,0 +1,21 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: template-single + labels: + app: template-single +spec: + replicas: 1 + selector: + matchLabels: + app: template-single + template: + metadata: + labels: + app: template-single + spec: + containers: + - name : main + image: template-single + imagePullPolicy: Always + diff --git a/manifest/deploy/kustomize/base/kustomization.yaml b/manifest/deploy/kustomize/base/kustomization.yaml new file mode 100644 index 0000000..302d92d --- /dev/null +++ b/manifest/deploy/kustomize/base/kustomization.yaml @@ -0,0 +1,8 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: +- deployment.yaml +- service.yaml + + + diff --git a/manifest/deploy/kustomize/base/service.yaml b/manifest/deploy/kustomize/base/service.yaml new file mode 100644 index 0000000..608771c --- /dev/null +++ b/manifest/deploy/kustomize/base/service.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Service +metadata: + name: template-single +spec: + ports: + - port: 80 + protocol: TCP + targetPort: 8000 + selector: + app: template-single + diff --git a/manifest/deploy/kustomize/overlays/develop/configmap.yaml b/manifest/deploy/kustomize/overlays/develop/configmap.yaml new file mode 100644 index 0000000..3b1d0af --- /dev/null +++ b/manifest/deploy/kustomize/overlays/develop/configmap.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: template-single-configmap +data: + config.yaml: | + server: + address: ":8000" + openapiPath: "/api.json" + swaggerPath: "/swagger" + + logger: + level : "all" + stdout: true diff --git a/manifest/deploy/kustomize/overlays/develop/deployment.yaml b/manifest/deploy/kustomize/overlays/develop/deployment.yaml new file mode 100644 index 0000000..04e4851 --- /dev/null +++ b/manifest/deploy/kustomize/overlays/develop/deployment.yaml @@ -0,0 +1,10 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: template-single +spec: + template: + spec: + containers: + - name : main + image: template-single:develop \ No newline at end of file diff --git a/manifest/deploy/kustomize/overlays/develop/kustomization.yaml b/manifest/deploy/kustomize/overlays/develop/kustomization.yaml new file mode 100644 index 0000000..4731c47 --- /dev/null +++ b/manifest/deploy/kustomize/overlays/develop/kustomization.yaml @@ -0,0 +1,14 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: +- ../../base +- configmap.yaml + +patchesStrategicMerge: +- deployment.yaml + +namespace: default + + + diff --git a/manifest/docker/Dockerfile b/manifest/docker/Dockerfile new file mode 100644 index 0000000..d3abe8f --- /dev/null +++ b/manifest/docker/Dockerfile @@ -0,0 +1,16 @@ +FROM loads/alpine:3.8 + +############################################################################### +# INSTALLATION +############################################################################### + +ENV WORKDIR /app +ADD resource $WORKDIR/ +ADD ./temp/linux_amd64/main $WORKDIR/main +RUN chmod +x $WORKDIR/main + +############################################################################### +# START +############################################################################### +WORKDIR $WORKDIR +CMD ./main diff --git a/manifest/docker/docker.sh b/manifest/docker/docker.sh new file mode 100644 index 0000000..ff393f9 --- /dev/null +++ b/manifest/docker/docker.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +# This shell is executed before docker build. + + + + +