chore: 初始开源发布
iotlcm-openapi:对外 OpenAPI 网关(V3 接口 + 旧版兼容接口)。
- Go module:iotlcm-openapi → git.5iot.com/iotlcm-public/iotlcm-openapi(178 文件)
- 凭据与地址脱敏:JWT signingKey、MySQL DSN、Redis 口令、
RabbitMQ DSN 全部改为 ${*} 占位;私网 IP → 文档地址段
- 7 处真实手机号 → 合法 11 位编造号
- 启动 banner 统一 IOTLCM 方块字 + 社区惯例署名
- 移除内部资产:CI 触发器
- Dockerfile 改用公共基础镜像,移除 GOPRIVATE 内网设置
- README 重写(原为 GoFrame 模板占位文案),补 Apache-2.0 LICENSE,
LF 行尾 + .gitattributes
This commit is contained in:
@@ -0,0 +1,426 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-openapi/internal/consts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-openapi/internal/dao"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-openapi/internal/model"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-openapi/internal/service"
|
||||
"math"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
)
|
||||
|
||||
type sQuerySim struct {
|
||||
}
|
||||
|
||||
func NewQuerySim() *sQuerySim {
|
||||
return &sQuerySim{}
|
||||
}
|
||||
|
||||
func init() {
|
||||
service.RegisterQuerySim(NewQuerySim())
|
||||
}
|
||||
|
||||
func (s *sQuerySim) querySim(ctx context.Context, input *model.QuerySimInput) (output *model.QuerySimInfoCard, err error) {
|
||||
if input.Iccid == "" && input.Msisdn == "" && input.Imsi == "" {
|
||||
g.Log().Debugf(ctx, "QuerySim.QuerySimCard, Iccid, Msisdn, Imsi are all empty, input: %v", input)
|
||||
return nil, gerror.New("Iccid, Msisdn, Imsi 不能全为空")
|
||||
}
|
||||
field := ""
|
||||
value := ""
|
||||
if input.Iccid != "" {
|
||||
field = dao.TSimCardInfo.Columns().Iccid
|
||||
value = input.Iccid
|
||||
} else if input.Msisdn != "" {
|
||||
field = dao.TSimCardInfo.Columns().Msisdn
|
||||
value = input.Msisdn
|
||||
} else if input.Imsi != "" {
|
||||
field = dao.TSimCardInfo.Columns().Imsi
|
||||
value = input.Imsi
|
||||
}
|
||||
|
||||
cardInfoTable := dao.TSimCardInfo.Table()
|
||||
sysDept := dao.SysDept.Table()
|
||||
flowPackage := dao.TSimFlowPackageInfo.Table()
|
||||
packageCategory := dao.TSimPackageCategory.Table()
|
||||
|
||||
err = service.IotTSimCardInfo().
|
||||
Model(ctx).
|
||||
Where(field, value).
|
||||
LeftJoin(sysDept, sysDept+"."+dao.SysDept.Columns().DeptId+"="+cardInfoTable+"."+dao.TSimCardInfo.Columns().DeptId).
|
||||
LeftJoin(flowPackage, flowPackage+"."+dao.TSimFlowPackageInfo.Columns().PackageCode+"="+cardInfoTable+"."+dao.TSimCardInfo.Columns().CurrentPackageCode).
|
||||
LeftJoin(packageCategory, packageCategory+"."+dao.TSimPackageCategory.Columns().CategoryCode+"="+flowPackage+"."+dao.TSimFlowPackageInfo.Columns().CategoryCode).
|
||||
Fields(
|
||||
cardInfoTable+".*",
|
||||
sysDept+"."+dao.SysDept.Columns().DeptName+" as DeptName",
|
||||
sysDept+"."+dao.SysDept.Columns().DeptKey+" as DeptKey",
|
||||
sysDept+"."+dao.SysDept.Columns().AreaCode+" as DeptAreaCode",
|
||||
flowPackage+"."+dao.TSimFlowPackageInfo.Columns().PackageName+" as PackageName",
|
||||
flowPackage+"."+dao.TSimFlowPackageInfo.Columns().CategoryCode+" as CategoryCode",
|
||||
packageCategory+"."+dao.TSimPackageCategory.Columns().CategoryName+" as CategoryName",
|
||||
).
|
||||
Limit(1).
|
||||
Scan(&output)
|
||||
if err != nil {
|
||||
g.Log().Debugf(ctx, "QuerySim.QuerySimCard, List sim card info failed, input: %v, err: %v", input, err)
|
||||
return nil, gerror.New("查询sim卡信息失败,请确认ICCID,MSISDN,IMSI是否正确")
|
||||
}
|
||||
if output == nil {
|
||||
return nil, gerror.New("查询sim卡信息失败,请确认ICCID,MSISDN,IMSI是否正确")
|
||||
}
|
||||
return output, nil
|
||||
}
|
||||
|
||||
func queryDate(t *gtime.Time) string {
|
||||
if t == nil || t.IsZero() {
|
||||
return ""
|
||||
}
|
||||
return t.Format("Y-m-d")
|
||||
}
|
||||
|
||||
func queryDateTime(t *gtime.Time) string {
|
||||
if t == nil || t.IsZero() {
|
||||
return ""
|
||||
}
|
||||
return t.Format("Y-m-d H:i:s")
|
||||
}
|
||||
|
||||
func queryRemaining(total, used float64) string {
|
||||
remaining := total - used
|
||||
if remaining < 0 {
|
||||
remaining = 0
|
||||
}
|
||||
return fmt.Sprintf("%.1f", remaining)
|
||||
}
|
||||
|
||||
func queryRemainingInt(total, used int) string {
|
||||
remaining := total - used
|
||||
if remaining < 0 {
|
||||
remaining = 0
|
||||
}
|
||||
return fmt.Sprintf("%d", remaining)
|
||||
}
|
||||
|
||||
func queryValidityPeriod(begin, end *gtime.Time) int {
|
||||
if begin == nil || begin.IsZero() || end == nil || end.IsZero() {
|
||||
return 0
|
||||
}
|
||||
beginTime := begin.Time
|
||||
endTime := end.Time
|
||||
months := (endTime.Year()-beginTime.Year())*12 + int(endTime.Month()-beginTime.Month())
|
||||
if endTime.Day() >= beginTime.Day()-1 {
|
||||
months++
|
||||
}
|
||||
if months < 0 {
|
||||
return 0
|
||||
}
|
||||
return months
|
||||
}
|
||||
|
||||
func possibleRegionCodes(regionCode string) []string {
|
||||
seen := map[string]struct{}{}
|
||||
codes := make([]string, 0, 4)
|
||||
add := func(code string) {
|
||||
if code == "" {
|
||||
return
|
||||
}
|
||||
if _, ok := seen[code]; ok {
|
||||
return
|
||||
}
|
||||
seen[code] = struct{}{}
|
||||
codes = append(codes, code)
|
||||
}
|
||||
|
||||
add(regionCode)
|
||||
if len(regionCode) == 12 {
|
||||
if regionCode[0:2] != "00" {
|
||||
add(regionCode[0:2] + "0000000000")
|
||||
}
|
||||
if regionCode[2:4] != "00" {
|
||||
add(regionCode[0:4] + "00000000")
|
||||
}
|
||||
if regionCode[4:6] != "00" {
|
||||
add(regionCode[0:6] + "000000")
|
||||
}
|
||||
} else {
|
||||
if len(regionCode) >= 4 {
|
||||
add(regionCode[:2] + "0000")
|
||||
}
|
||||
if len(regionCode) >= 6 {
|
||||
add(regionCode[:4] + "00")
|
||||
}
|
||||
}
|
||||
return codes
|
||||
}
|
||||
|
||||
func (s *sQuerySim) queryLocation(ctx context.Context, regionCode string) string {
|
||||
regionCode = strings.TrimSpace(regionCode)
|
||||
if regionCode == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
type division struct {
|
||||
RegionCode string `json:"regionCode"`
|
||||
RegionName string `json:"regionName"`
|
||||
RegionParent string `json:"regionParent"`
|
||||
}
|
||||
|
||||
var divisions []*division
|
||||
err := dao.TDivisionCode.Ctx(ctx).
|
||||
WhereIn(dao.TDivisionCode.Columns().RegionCode, possibleRegionCodes(regionCode)).
|
||||
Scan(&divisions)
|
||||
if err != nil || len(divisions) == 0 {
|
||||
return regionCode
|
||||
}
|
||||
|
||||
byCode := make(map[string]*division, len(divisions))
|
||||
for _, item := range divisions {
|
||||
byCode[item.RegionCode] = item
|
||||
}
|
||||
item := byCode[regionCode]
|
||||
if item == nil {
|
||||
return regionCode
|
||||
}
|
||||
|
||||
names := make([]string, 0, 4)
|
||||
for item != nil {
|
||||
if item.RegionName != "" {
|
||||
names = append([]string{item.RegionName}, names...)
|
||||
}
|
||||
if item.RegionParent == "" || item.RegionParent == "0" {
|
||||
break
|
||||
}
|
||||
item = byCode[item.RegionParent]
|
||||
}
|
||||
if len(names) == 0 {
|
||||
return regionCode
|
||||
}
|
||||
if names[0] != "中国" {
|
||||
names = append([]string{"中国"}, names...)
|
||||
}
|
||||
return strings.Join(names, "/")
|
||||
}
|
||||
|
||||
func (s *sQuerySim) queryLocationByAreaCode(ctx context.Context, areaCode string) string {
|
||||
areaCode = strings.TrimSpace(areaCode)
|
||||
if areaCode == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
type division struct {
|
||||
RegionCode string `json:"regionCode"`
|
||||
RegionName string `json:"regionName"`
|
||||
RegionParent string `json:"regionParent"`
|
||||
}
|
||||
|
||||
byCode := make(map[string]*division)
|
||||
currentCode := ""
|
||||
for _, code := range possibleRegionCodes(areaCode) {
|
||||
var item *division
|
||||
err := dao.TDivisionCode.Ctx(ctx).
|
||||
Where(dao.TDivisionCode.Columns().RegionCode, code).
|
||||
Scan(&item)
|
||||
if err != nil {
|
||||
return areaCode
|
||||
}
|
||||
if item == nil {
|
||||
continue
|
||||
}
|
||||
byCode[item.RegionCode] = item
|
||||
currentCode = item.RegionCode
|
||||
break
|
||||
}
|
||||
if currentCode == "" {
|
||||
return areaCode
|
||||
}
|
||||
|
||||
names := make([]string, 0, 4)
|
||||
for currentCode != "" {
|
||||
item := byCode[currentCode]
|
||||
if item == nil {
|
||||
err := dao.TDivisionCode.Ctx(ctx).
|
||||
Where(dao.TDivisionCode.Columns().RegionCode, currentCode).
|
||||
Scan(&item)
|
||||
if err != nil {
|
||||
return areaCode
|
||||
}
|
||||
if item == nil {
|
||||
break
|
||||
}
|
||||
byCode[item.RegionCode] = item
|
||||
}
|
||||
if item.RegionName != "" {
|
||||
names = append([]string{item.RegionName}, names...)
|
||||
}
|
||||
if item.RegionParent == "" || item.RegionParent == "0" {
|
||||
break
|
||||
}
|
||||
currentCode = item.RegionParent
|
||||
}
|
||||
if len(names) == 0 {
|
||||
return areaCode
|
||||
}
|
||||
if names[0] != "中国" {
|
||||
names = append([]string{"中国"}, names...)
|
||||
}
|
||||
return strings.Join(names, "/")
|
||||
}
|
||||
|
||||
func (s *sQuerySim) QuerySimInfo(ctx context.Context, input *model.QuerySimInput) (output *model.QuerySimInfoOutput, err error) {
|
||||
g.Log().Error(ctx, input)
|
||||
simCard, err := s.querySim(ctx, input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
output = &model.QuerySimInfoOutput{
|
||||
Iccid: simCard.Iccid,
|
||||
Msisdn: simCard.Msisdn,
|
||||
Imsi: simCard.Imsi,
|
||||
Imei: simCard.Imei,
|
||||
Username: simCard.DeptName,
|
||||
Location: s.queryLocationByAreaCode(ctx, simCard.DeptAreaCode),
|
||||
PlanCategory: simCard.CategoryName,
|
||||
CurrentPlanName: simCard.PackageName,
|
||||
Plangroup: "",
|
||||
Planid: simCard.CurrentPackageCode,
|
||||
Appid: simCard.DeptKey,
|
||||
AllowPlanChange: 0,
|
||||
LastSyncTime: queryDateTime(simCard.StatusSyncTime),
|
||||
AccessType: consts.V3ApiSimAccessTypeNameMap[simCard.AccessType],
|
||||
CardStatus: consts.V3ApiSimBossCardStatusNameMap[simCard.BossCardStatus],
|
||||
CardType: consts.V3ApiSimCardTypeNameMap[simCard.CardType],
|
||||
NetworkType: consts.V3ApiSimNetModelNameMap[simCard.NetModel],
|
||||
ActivationDate: queryDate(simCard.OutStockTime),
|
||||
ActiveDate: queryDate(simCard.FlowPkgBeginAt),
|
||||
ValidityPeriod: queryValidityPeriod(simCard.FlowPkgBeginAt, simCard.FlowPkgEndAt),
|
||||
ExpiryDate: queryDate(simCard.FlowPkgEndAt),
|
||||
TestPeriod: 0,
|
||||
TestPeriodExpiryDate: nil,
|
||||
SilentPeriod: 0,
|
||||
SilentPeriodExpiryDate: nil,
|
||||
DataService: simCard.DataServiceStatus,
|
||||
PlanData: int(math.Round(simCard.PackageTotalFlowMb)),
|
||||
UsedData: simCard.UsedFlowMb,
|
||||
RemainingData: queryRemaining(simCard.PackageTotalFlowMb, simCard.UsedFlowMb),
|
||||
SmsService: simCard.SmsServiceStatus,
|
||||
PlanSMS: simCard.PackageTotalSms,
|
||||
UsedSMS: simCard.UsedSms,
|
||||
RemainingSMS: queryRemainingInt(simCard.PackageTotalSms, simCard.UsedSms),
|
||||
VoiceService: simCard.VoiceServiceStatus,
|
||||
PlanMinutes: simCard.PackageTotalVoice,
|
||||
UsedMinutes: simCard.UsedVoice,
|
||||
RemainingMinutes: queryRemainingInt(simCard.PackageTotalVoice, simCard.UsedVoice),
|
||||
InternationalRoamingService: simCard.RoamServiceStatus,
|
||||
PlanInternationalData: int(math.Round(simCard.PackageTotalRoamFlowMb)),
|
||||
UsedInternationalData: int(math.Round(simCard.UsedRoamFlowMb)),
|
||||
RemainingInternationalData: queryRemaining(simCard.PackageTotalRoamFlowMb, simCard.UsedRoamFlowMb),
|
||||
Description: nil,
|
||||
}
|
||||
return output, nil
|
||||
}
|
||||
|
||||
func (s *sQuerySim) QuerySimLimit(ctx context.Context, input *model.QuerySimInput) (output *model.QuerySimLimitOutput, err error) {
|
||||
simCard, err := s.querySim(ctx, input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
trafficCutoffName := "删除"
|
||||
if simCard.TrafficCutoffStatus == 1 {
|
||||
trafficCutoffName = "已开启"
|
||||
} else if simCard.TrafficCutoffStatus == 2 {
|
||||
trafficCutoffName = "已关闭"
|
||||
}
|
||||
output = &model.QuerySimLimitOutput{
|
||||
TrafficCutoffStatus: simCard.TrafficCutoffStatus,
|
||||
TrafficCutoffName: trafficCutoffName,
|
||||
TrafficCutoffThreshold: simCard.TrafficCutoffThreshold,
|
||||
}
|
||||
return output, nil
|
||||
}
|
||||
|
||||
func (s *sQuerySim) QuerySimImei(ctx context.Context, input *model.QuerySimInput) (output *model.QuerySimImeiOutput, err error) {
|
||||
simCard, err := s.querySim(ctx, input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if imei := strings.TrimSpace(simCard.Imei); imei != "" {
|
||||
return &model.QuerySimImeiOutput{Imei: imei}, nil
|
||||
}
|
||||
|
||||
upstreamInput := &model.SimImeiInput{
|
||||
SimKeys: []*model.SimKey{{
|
||||
ChanKey: strings.TrimSpace(simCard.ChanKey),
|
||||
ICCID: strings.TrimSpace(simCard.Iccid),
|
||||
IMSI: strings.TrimSpace(simCard.Imsi),
|
||||
MSISDN: strings.TrimSpace(simCard.Msisdn),
|
||||
}},
|
||||
}
|
||||
upstreamResults, err := service.UpstreamChan().QuerySimImei(ctx, upstreamInput)
|
||||
if err != nil {
|
||||
g.Log().Warningf(ctx, "QuerySim.QuerySimImei upstream query failed, iccid: %s, err: %v", simCard.Iccid, err)
|
||||
return &model.QuerySimImeiOutput{Imei: ""}, nil
|
||||
}
|
||||
for _, item := range upstreamResults {
|
||||
if item.Code != 0 {
|
||||
continue
|
||||
}
|
||||
if imei := strings.TrimSpace(item.ResData.Imei); imei != "" {
|
||||
columns := dao.TSimCardInfo.Columns()
|
||||
// imei_update_time 记录"值实际变更"的时间(单卡回填必然是从空到有值,
|
||||
// 故一定发生变更);imei_sync_time 专属于 worker 轮询链路,
|
||||
// 此处不写,避免污染"轮询覆盖度"指标。
|
||||
if _, updateErr := service.IotTSimCardInfo().
|
||||
Model(ctx).
|
||||
Where(columns.Id, simCard.Id).
|
||||
Data(g.Map{
|
||||
columns.Imei: imei,
|
||||
columns.ImeiUpdateTime: gtime.Now(),
|
||||
}).
|
||||
Update(); updateErr != nil {
|
||||
g.Log().Warningf(ctx, "QuerySim.QuerySimImei persist upstream imei failed, card id: %d, err: %v", simCard.Id, updateErr)
|
||||
}
|
||||
return &model.QuerySimImeiOutput{Imei: imei}, nil
|
||||
}
|
||||
}
|
||||
|
||||
output = &model.QuerySimImeiOutput{
|
||||
Imei: "",
|
||||
}
|
||||
return output, nil
|
||||
}
|
||||
|
||||
func (s *sQuerySim) QuerySimUsage(ctx context.Context, input *model.QuerySimInput) (output *model.QuerySimUsageOutput, err error) {
|
||||
simCard, err := s.querySim(ctx, input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
output = &model.QuerySimUsageOutput{
|
||||
UseUnit: "MB",
|
||||
UseAmount: simCard.UsedFlowMb,
|
||||
}
|
||||
return output, nil
|
||||
}
|
||||
|
||||
// QuerySimStatus 查询SIM卡状态(纯读本地库,不调用上游通道)
|
||||
func (s *sQuerySim) QuerySimStatus(ctx context.Context, input *model.QuerySimInput) (output *model.QuerySimCardStatusOutput, err error) {
|
||||
simCard, err := s.querySim(ctx, input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
statusName, ok := consts.V3ApiSimBossCardStatusNameMap[simCard.BossCardStatus]
|
||||
if !ok {
|
||||
statusName = "未知"
|
||||
}
|
||||
output = &model.QuerySimCardStatusOutput{
|
||||
Iccid: simCard.Iccid,
|
||||
LifeCycle: simCard.BossCardStatus,
|
||||
LifeCycleName: statusName,
|
||||
}
|
||||
return output, nil
|
||||
}
|
||||
Reference in New Issue
Block a user