Files
iotlcm/internal/logic/query/sim.go
T
MiFaZhan 88dfe5c640 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
2026-10-23 14:15:47 +08:00

427 lines
13 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}