4e2cd43a55
iotlcm-onelink-mock:OneLink 挡板服务(本地测试替身)。 - Go module 迁移至 git.5iot.com/iotlcm-public/iotlcm-onelink-mock - 修复同毫秒内多次生成卡导致 ICCID/IMSI 唯一约束冲突 - 补 Apache-2.0 LICENSE,README 重写并新增 docs/deploy.md - 移除内部部署资产(CI 触发器 / 镜像推送脚本 / 指定环境部署文档) - 测试凭据改为 demo_appid / demo_password,真实手机号与内网 IP 脱敏 - 统一 LF 行尾并加 .gitattributes
747 lines
22 KiB
Go
747 lines
22 KiB
Go
package service
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"math"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type Service struct {
|
|
store *StoreManager
|
|
operationLogs *OperationLogStore
|
|
runtimeLogs *RuntimeLogStore
|
|
}
|
|
|
|
func NewService(dataPath string, runtimeLogStores ...*RuntimeLogStore) (*Service, error) {
|
|
store, err := NewStoreManager(dataPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
runtimeLogs := NewRuntimeLogStore()
|
|
if len(runtimeLogStores) > 0 && runtimeLogStores[0] != nil {
|
|
runtimeLogs = runtimeLogStores[0]
|
|
}
|
|
return &Service{store: store, operationLogs: NewOperationLogStore(), runtimeLogs: runtimeLogs}, nil
|
|
}
|
|
|
|
func (s *Service) Router() http.Handler {
|
|
mux := http.NewServeMux()
|
|
mux.HandleFunc("/healthz", s.health)
|
|
mux.HandleFunc("/api/state", s.adminState)
|
|
mux.HandleFunc("/api/appids", s.adminAppIDs)
|
|
mux.HandleFunc("/api/cards", s.adminCards)
|
|
mux.HandleFunc("/api/card-groups", s.adminCardGroups)
|
|
mux.HandleFunc("/api/cards/export", s.adminCardsExport)
|
|
mux.HandleFunc("/api/settings", s.adminSettings)
|
|
mux.HandleFunc("/api/orders", s.adminOrders)
|
|
mux.HandleFunc("/api/operation-logs", s.adminOperationLogs)
|
|
mux.HandleFunc("/api/runtime-logs", s.adminRuntimeLogs)
|
|
mux.HandleFunc("/api/sync", s.adminSync)
|
|
mux.HandleFunc("/api/generate", s.adminGenerate)
|
|
mux.HandleFunc("/sim/", s.chanAPI)
|
|
mux.HandleFunc("/v5/", s.oneLink)
|
|
mux.HandleFunc("/ec/", s.oneLink)
|
|
return withCORS(s.withOperationLogging(mux))
|
|
}
|
|
|
|
func withCORS(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
|
|
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
|
|
if r.Method == http.MethodOptions {
|
|
w.WriteHeader(http.StatusNoContent)
|
|
return
|
|
}
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
|
|
func (s *Service) health(w http.ResponseWriter, r *http.Request) {
|
|
writeJSON(w, http.StatusOK, map[string]string{"status": "ok"})
|
|
}
|
|
|
|
func (s *Service) oneLink(w http.ResponseWriter, r *http.Request) {
|
|
path := strings.TrimPrefix(r.URL.Path, "/v5/")
|
|
path = strings.TrimPrefix(path, "/")
|
|
switch path {
|
|
case "ec/get/token":
|
|
s.getToken(w, r)
|
|
case "ec/query/sim-basic-info":
|
|
s.withToken(w, r, s.queryBasicInfo)
|
|
case "ec/query/sim-session":
|
|
s.withToken(w, r, s.querySession)
|
|
case "ec/query/sim-status":
|
|
s.withToken(w, r, s.queryStatus)
|
|
case "ec/query/sim-stop-reason":
|
|
s.withToken(w, r, s.queryStopReason)
|
|
case "ec/query/sim-frozen-state-info":
|
|
s.withToken(w, r, s.queryFrozenState)
|
|
case "ec/query/sim-apn-session-info":
|
|
s.withToken(w, r, s.queryAPNSession)
|
|
case "ec/query/sim-activated-features-info":
|
|
s.withToken(w, r, s.queryActivatedFeatures)
|
|
case "ec/query/sim-operating-status":
|
|
s.withToken(w, r, s.queryOperatingStatus)
|
|
case "ec/query/sim-data-diagnosis":
|
|
s.withToken(w, r, s.queryDataDiagnosis)
|
|
case "ec/query/sim-imei":
|
|
s.withToken(w, r, s.queryIMEI)
|
|
case "ec/query/sim-data-usage-inpool":
|
|
s.withToken(w, r, s.queryDataUsageInPool)
|
|
case "ec/query/sim-data-usage":
|
|
s.withToken(w, r, s.queryDataUsage)
|
|
case "ec/query/sim-data-usage-monthly/batch":
|
|
s.withToken(w, r, s.queryDataUsageMonthlyBatch)
|
|
case "ec/query/sim-data-usage-daily/batch":
|
|
s.withToken(w, r, s.queryDataUsageDailyBatch)
|
|
case "ec/query/apn-info":
|
|
s.withToken(w, r, s.queryAPNInfo)
|
|
case "ec/query/card-bind-status":
|
|
s.withToken(w, r, s.queryCardBindStatus)
|
|
case "ec/query/on-off-status":
|
|
s.withToken(w, r, s.queryOnOffStatus)
|
|
case "ec/query/region-limit-status":
|
|
s.withToken(w, r, s.queryRegionLimitStatus)
|
|
case "ec/change/sim-status":
|
|
s.withToken(w, r, s.changeSIMStatus)
|
|
case "ec/operate/sim-apn-function":
|
|
s.withToken(w, r, s.operateSIMAPNFunction)
|
|
default:
|
|
writeOneLink(w, Response{Status: "14008", Message: "查询不到信息", Result: []interface{}{}})
|
|
}
|
|
}
|
|
|
|
func (s *Service) getToken(w http.ResponseWriter, r *http.Request) {
|
|
q := r.URL.Query()
|
|
appid := q.Get("appid")
|
|
password := q.Get("password")
|
|
refresh := q.Get("refresh")
|
|
var result Response
|
|
|
|
err := s.store.SaveMutation(func(st *Store) error {
|
|
for i := range st.AppIDs {
|
|
cred := &st.AppIDs[i]
|
|
if cred.AppID != appid || !cred.Enabled {
|
|
continue
|
|
}
|
|
if cred.Password != password {
|
|
result = Response{Status: "12022", Message: "PASSWORD 鉴权不通过", Result: []interface{}{}}
|
|
return nil
|
|
}
|
|
now := time.Now()
|
|
if refresh == "0" {
|
|
if token, ok := st.Tokens[appid]; ok && token.ExpiresAt.After(now) {
|
|
ttl := int(time.Until(token.ExpiresAt).Seconds())
|
|
result = okResp(map[string]string{"token": token.Value, "ttl": strconv.Itoa(ttl)})
|
|
return nil
|
|
}
|
|
}
|
|
token := Token{
|
|
AppID: appid,
|
|
Value: randomHex(32),
|
|
ExpiresAt: now.Add(time.Hour),
|
|
}
|
|
st.Tokens[appid] = token
|
|
cred.LastToken = token.Value
|
|
cred.TokenExpires = token.ExpiresAt.Format("2006-01-02 15:04:05")
|
|
result = okResp(map[string]string{"token": token.Value, "ttl": "3600"})
|
|
return nil
|
|
}
|
|
result = Response{Status: "12021", Message: "认证无效", Result: []interface{}{}}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
writeOneLink(w, Response{Status: "19000", Message: err.Error(), Result: []interface{}{}})
|
|
return
|
|
}
|
|
writeOneLink(w, result)
|
|
}
|
|
|
|
func (s *Service) withToken(w http.ResponseWriter, r *http.Request, next func(http.ResponseWriter, *http.Request, Store)) {
|
|
token := r.URL.Query().Get("token")
|
|
st := s.store.Snapshot()
|
|
for _, t := range st.Tokens {
|
|
if t.Value == token && t.ExpiresAt.After(time.Now()) {
|
|
next(w, r, st)
|
|
return
|
|
}
|
|
}
|
|
writeOneLink(w, Response{Status: "12021", Message: "TOKEN 不存在或已过期,请重新获取", Result: []interface{}{}})
|
|
}
|
|
|
|
func (s *Service) queryBasicInfo(w http.ResponseWriter, r *http.Request, st Store) {
|
|
card, err := findSingleCard(r, st)
|
|
if err != nil {
|
|
writeOneLink(w, paramErr(err.Error()))
|
|
return
|
|
}
|
|
writeOneLink(w, okResp(map[string]string{
|
|
"msisdn": card.MSISDN, "imsi": card.IMSI, "iccid": card.ICCID,
|
|
"activeDate": card.ActiveDate, "openDate": card.OpenDate, "remark": card.Remark,
|
|
}))
|
|
}
|
|
|
|
func (s *Service) querySession(w http.ResponseWriter, r *http.Request, st Store) {
|
|
card, err := findSingleCard(r, st)
|
|
if err != nil {
|
|
writeOneLink(w, paramErr(err.Error()))
|
|
return
|
|
}
|
|
writeOneLink(w, okResp(map[string]interface{}{
|
|
"simSessionList": []map[string]string{{
|
|
"apnId": "CMIOT", "status": card.SessionStatus, "ip": card.IP,
|
|
"ipv6Prefix": card.IPv6Prefix, "ipv6": card.IPv6,
|
|
"createDate": card.LastChangeDate, "rat": card.RAT,
|
|
}},
|
|
}))
|
|
}
|
|
|
|
func (s *Service) queryStatus(w http.ResponseWriter, r *http.Request, st Store) {
|
|
card, err := findSingleCard(r, st)
|
|
if err != nil {
|
|
writeOneLink(w, paramErr(err.Error()))
|
|
return
|
|
}
|
|
writeOneLink(w, okResp(map[string]string{"cardStatus": card.CardStatus, "lastChangeDate": card.LastChangeDate}))
|
|
}
|
|
|
|
func (s *Service) queryStopReason(w http.ResponseWriter, r *http.Request, st Store) {
|
|
card, err := findSingleCard(r, st)
|
|
if err != nil {
|
|
writeOneLink(w, paramErr(err.Error()))
|
|
return
|
|
}
|
|
stopReason := card.StopReason
|
|
if stopReason == "" {
|
|
stopReason = defaultStopReason(card.CardStatus)
|
|
}
|
|
writeOneLink(w, okResp(map[string]string{"platformType": "OneLink-CT", "stopReason": stopReason}))
|
|
}
|
|
|
|
func (s *Service) queryFrozenState(w http.ResponseWriter, r *http.Request, st Store) {
|
|
card, err := findSingleCard(r, st)
|
|
if err != nil {
|
|
writeOneLink(w, paramErr(err.Error()))
|
|
return
|
|
}
|
|
frozen := card.FrozenStatus
|
|
if frozen == "" {
|
|
frozen = "0"
|
|
}
|
|
operTime := card.FrozenOperTime
|
|
if operTime == "" {
|
|
operTime = diagnosticTime(parseCardTime(card.LastChangeDate))
|
|
}
|
|
data := map[string]string{"frozenStatus": frozen, "operTime": operTime}
|
|
if frozen == "1" && card.FrozenReason != "" {
|
|
data["frozenReason"] = card.FrozenReason
|
|
}
|
|
writeOneLink(w, okResp(data))
|
|
}
|
|
|
|
func (s *Service) queryAPNSession(w http.ResponseWriter, r *http.Request, st Store) {
|
|
card, err := findSingleCard(r, st)
|
|
if err != nil {
|
|
writeOneLink(w, paramErr(err.Error()))
|
|
return
|
|
}
|
|
items := make([]map[string]string, 0, len(card.APNs))
|
|
for _, apn := range card.APNs {
|
|
status := card.SessionStatus
|
|
if status != "00" && status != "01" {
|
|
status = "00"
|
|
}
|
|
createTime := card.SessionCreateDate
|
|
if createTime == "" {
|
|
createTime = card.LastChangeDate
|
|
}
|
|
items = append(items, map[string]string{"apn": apn.Name, "sessionStatus": status, "ipv4": card.IP, "ipv6Prefix": card.IPv6Prefix, "ipv6": card.IPv6, "createTime": createTime, "accessType": accessType(card.RAT)})
|
|
}
|
|
if len(items) == 0 {
|
|
status := card.SessionStatus
|
|
if status != "00" && status != "01" {
|
|
status = "00"
|
|
}
|
|
createTime := card.SessionCreateDate
|
|
if createTime == "" {
|
|
createTime = card.LastChangeDate
|
|
}
|
|
items = append(items, map[string]string{"apn": "CMIOT", "sessionStatus": status, "ipv4": card.IP, "ipv6Prefix": card.IPv6Prefix, "ipv6": card.IPv6, "createTime": createTime, "accessType": accessType(card.RAT)})
|
|
}
|
|
writeOneLink(w, okResp(map[string]interface{}{"infoList": items}))
|
|
}
|
|
|
|
func (s *Service) queryActivatedFeatures(w http.ResponseWriter, r *http.Request, st Store) {
|
|
card, err := findSingleCard(r, st)
|
|
if err != nil {
|
|
writeOneLink(w, paramErr(err.Error()))
|
|
return
|
|
}
|
|
items := make([]map[string]string, 0, len(card.APNs))
|
|
for _, apn := range card.APNs {
|
|
openStatus := "0"
|
|
if apn.Status == "1" {
|
|
openStatus = "1"
|
|
}
|
|
changeTime := apn.ModifyTime
|
|
if changeTime == "" {
|
|
changeTime = diagnosticTime(parseCardTime(card.LastChangeDate))
|
|
}
|
|
items = append(items, map[string]string{"functionType": "11", "openStatus": openStatus, "apn": apn.Name, "changeTime": changeTime})
|
|
}
|
|
if len(items) == 0 {
|
|
items = append(items, map[string]string{"functionType": "11", "openStatus": "1", "apn": "CMIOT", "changeTime": diagnosticTime(parseCardTime(card.LastChangeDate))})
|
|
}
|
|
writeOneLink(w, okResp(map[string]interface{}{"communicationFunctionList": items}))
|
|
}
|
|
|
|
func (s *Service) queryOperatingStatus(w http.ResponseWriter, r *http.Request, st Store) {
|
|
card, err := findSingleCard(r, st)
|
|
if err != nil {
|
|
writeOneLink(w, paramErr(err.Error()))
|
|
return
|
|
}
|
|
writeOneLink(w, okResp(map[string]string{"operatingStatus": card.OnOffStatus}))
|
|
}
|
|
|
|
func (s *Service) queryDataDiagnosis(w http.ResponseWriter, r *http.Request, st Store) {
|
|
card, err := findSingleCard(r, st)
|
|
if err != nil {
|
|
writeOneLink(w, paramErr(err.Error()))
|
|
return
|
|
}
|
|
apnInfo := make([]map[string]interface{}, 0, len(card.APNs))
|
|
for _, apn := range card.APNs {
|
|
status := apn.DiagnosisStatus
|
|
if status == "" {
|
|
status = "2"
|
|
}
|
|
modifyTime := apn.ModifyTime
|
|
if modifyTime == "" {
|
|
modifyTime = diagnosticTime(parseCardTime(card.LastChangeDate))
|
|
}
|
|
usageState := apn.ServiceUsageState
|
|
if usageState == "" {
|
|
usageState = "1"
|
|
}
|
|
item := map[string]interface{}{"apnName": apn.Name, "apnStatus": status, "modifyTime": modifyTime, "apnServiceUsageState": usageState}
|
|
if status == "4" && apn.OverUseDetails != "" {
|
|
item["overUseDetails"] = apn.OverUseDetails
|
|
}
|
|
pccInfo := make([]map[string]string, 0, len(apn.PCCs))
|
|
for _, pcc := range apn.PCCs {
|
|
pccTime := pcc.ModifyTime
|
|
if pccTime == "" {
|
|
pccTime = modifyTime
|
|
}
|
|
pccInfo = append(pccInfo, map[string]string{"pccServiceCode": pcc.ServiceCode, "pccName": pcc.Name, "pccPolicyStatus": pcc.PolicyStatus, "pccServiceUsageState": pcc.ServiceUsageState, "modifyTime": pccTime})
|
|
}
|
|
item["pccList"] = map[string]interface{}{"pccInfo": pccInfo}
|
|
apnInfo = append(apnInfo, item)
|
|
}
|
|
if len(apnInfo) == 0 {
|
|
apnInfo = append(apnInfo, map[string]interface{}{"apnName": "CMIOT", "apnStatus": "2", "modifyTime": diagnosticTime(parseCardTime(card.LastChangeDate)), "apnServiceUsageState": "1", "pccList": map[string]interface{}{"pccInfo": []map[string]string{}}})
|
|
}
|
|
writeOneLink(w, okResp(map[string]interface{}{"apnList": map[string]interface{}{"apnInfo": apnInfo}}))
|
|
}
|
|
|
|
func accessType(rat string) string {
|
|
switch strings.ToUpper(rat) {
|
|
case "5G", "51", "SA":
|
|
return "51"
|
|
case "NSA", "10":
|
|
return "10"
|
|
case "REDCAP", "58":
|
|
return "58"
|
|
case "NB", "8":
|
|
return "8"
|
|
case "2G", "2":
|
|
return "2"
|
|
case "3G", "3":
|
|
return "1"
|
|
default:
|
|
return "6"
|
|
}
|
|
}
|
|
|
|
func (s *Service) queryIMEI(w http.ResponseWriter, r *http.Request, st Store) {
|
|
card, err := findSingleCard(r, st)
|
|
if err != nil {
|
|
writeOneLink(w, paramErr(err.Error()))
|
|
return
|
|
}
|
|
writeOneLink(w, okResp(map[string]string{"imei": card.IMEI}))
|
|
}
|
|
|
|
func (s *Service) queryDataUsageInPool(w http.ResponseWriter, r *http.Request, st Store) {
|
|
card, err := findSingleCard(r, st)
|
|
if err != nil {
|
|
writeOneLink(w, paramErr(err.Error()))
|
|
return
|
|
}
|
|
apns := make([]map[string]string, 0, len(card.APNs))
|
|
for _, apn := range card.APNs {
|
|
apns = append(apns, map[string]string{"apnName": apn.Name, "apnUseAmount": fmtAmount(apn.UsageBytes)})
|
|
}
|
|
writeOneLink(w, okResp(map[string]interface{}{"apnList": apns}))
|
|
}
|
|
|
|
func (s *Service) queryDataUsage(w http.ResponseWriter, r *http.Request, st Store) {
|
|
card, err := findSingleCard(r, st)
|
|
if err != nil {
|
|
writeOneLink(w, paramErr(err.Error()))
|
|
return
|
|
}
|
|
writeOneLink(w, okResp(map[string]interface{}{
|
|
"dataAmount": fmt.Sprintf("%.2f", card.MonthUsageKB),
|
|
"apnUseAmountList": apnUseAmountList(card),
|
|
}))
|
|
}
|
|
|
|
func (s *Service) queryDataUsageMonthlyBatch(w http.ResponseWriter, r *http.Request, st Store) {
|
|
cards, key, err := findBatchCards(r, st)
|
|
if err != nil {
|
|
writeOneLink(w, paramErr(err.Error()))
|
|
return
|
|
}
|
|
items := make([]map[string]interface{}, 0, len(cards))
|
|
for _, card := range cards {
|
|
amount := card.MonthUsageKB
|
|
if q := r.URL.Query().Get("queryDate"); len(q) == 6 && q != time.Now().Format("200601") {
|
|
amount = card.MonthlyBaseKB
|
|
}
|
|
items = append(items, batchUsageItem(card, key, amount))
|
|
}
|
|
writeOneLink(w, okResp(map[string]interface{}{
|
|
"deadLine": time.Now().AddDate(0, 0, -1).Format("20060102"),
|
|
"dataAmountList": items,
|
|
}))
|
|
}
|
|
|
|
func (s *Service) queryDataUsageDailyBatch(w http.ResponseWriter, r *http.Request, st Store) {
|
|
cards, key, err := findBatchCards(r, st)
|
|
if err != nil {
|
|
writeOneLink(w, paramErr(err.Error()))
|
|
return
|
|
}
|
|
items := make([]map[string]interface{}, 0, len(cards))
|
|
for _, card := range cards {
|
|
items = append(items, batchUsageItem(card, key, card.DailyBaseKB))
|
|
}
|
|
writeOneLink(w, okResp(map[string]interface{}{"dataAmountList": items}))
|
|
}
|
|
|
|
func (s *Service) queryAPNInfo(w http.ResponseWriter, r *http.Request, st Store) {
|
|
card, err := findSingleCard(r, st)
|
|
if err != nil {
|
|
writeOneLink(w, paramErr(err.Error()))
|
|
return
|
|
}
|
|
apns := make([]map[string]string, 0, len(card.APNs))
|
|
for _, apn := range card.APNs {
|
|
apns = append(apns, map[string]string{"apnName": apn.Name, "status": apn.Status})
|
|
}
|
|
writeOneLink(w, okResp(map[string]interface{}{"apnList": apns}))
|
|
}
|
|
|
|
func (s *Service) queryCardBindStatus(w http.ResponseWriter, r *http.Request, st Store) {
|
|
if r.URL.Query().Get("msisdn") == "" || r.URL.Query().Get("iccid") != "" || r.URL.Query().Get("imsi") != "" {
|
|
writeOneLink(w, paramErr("card-bind-status requires msisdn only"))
|
|
return
|
|
}
|
|
testType := r.URL.Query().Get("testType")
|
|
if testType != "0" && testType != "1" {
|
|
writeOneLink(w, paramErr("testType must be 0 or 1"))
|
|
return
|
|
}
|
|
card, err := findSingleCard(r, st)
|
|
if err != nil {
|
|
writeOneLink(w, paramErr(err.Error()))
|
|
return
|
|
}
|
|
result := card.CardBindStatus
|
|
if result != "0" && result != "1" && result != "2" {
|
|
result = "1"
|
|
}
|
|
data := map[string]string{"result": result}
|
|
if result == "2" {
|
|
data["errorCode"] = "MOCK_BIND_CHECK_FAILED"
|
|
data["errorDes"] = "mock bind check failed"
|
|
}
|
|
writeOneLink(w, okResp(data))
|
|
}
|
|
|
|
func (s *Service) queryOnOffStatus(w http.ResponseWriter, r *http.Request, st Store) {
|
|
card, err := findSingleCard(r, st)
|
|
if err != nil {
|
|
writeOneLink(w, paramErr(err.Error()))
|
|
return
|
|
}
|
|
writeOneLink(w, okResp(map[string]string{"status": card.OnOffStatus}))
|
|
}
|
|
|
|
func (s *Service) queryRegionLimitStatus(w http.ResponseWriter, r *http.Request, st Store) {
|
|
card, err := findSingleCard(r, st)
|
|
if err != nil {
|
|
writeOneLink(w, paramErr(err.Error()))
|
|
return
|
|
}
|
|
writeOneLink(w, okResp(map[string]string{"regionLimitStatus": card.RegionLimitStatus}))
|
|
}
|
|
|
|
func (s *Service) changeSIMStatus(w http.ResponseWriter, r *http.Request, st Store) {
|
|
q := r.URL.Query()
|
|
idKey, idVal, err := singleIdentifier(q)
|
|
if err != nil {
|
|
writeOneLink(w, paramErr(err.Error()))
|
|
return
|
|
}
|
|
operType := q.Get("operType")
|
|
orderNum := "ORD" + time.Now().Format("20060102150405") + randomHex(3)
|
|
var result map[string]string
|
|
err = s.store.SaveMutation(func(store *Store) error {
|
|
idx := findCardIndex(*store, idKey, idVal)
|
|
if idx < 0 {
|
|
return fmt.Errorf("查询不到信息")
|
|
}
|
|
card := &store.Cards[idx]
|
|
card.CardStatus = statusByOperType(operType, card.CardStatus)
|
|
card.LastChangeDate = time.Now().Format("2006-01-02 15:04:05")
|
|
store.Orders = append(store.Orders, Order{
|
|
OrderNum: orderNum, MSISDN: card.MSISDN, ICCID: card.ICCID, IMSI: card.IMSI,
|
|
Type: "sim-status", OperType: operType, Status: "completed", CreatedAt: card.LastChangeDate,
|
|
})
|
|
result = map[string]string{idKey: idVal, "orderNum": orderNum}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
writeOneLink(w, paramErr(err.Error()))
|
|
return
|
|
}
|
|
writeOneLink(w, okResp(result))
|
|
}
|
|
|
|
func (s *Service) operateSIMAPNFunction(w http.ResponseWriter, r *http.Request, st Store) {
|
|
q := r.URL.Query()
|
|
idKey, idVal, err := singleIdentifier(q)
|
|
if err != nil {
|
|
writeOneLink(w, paramErr(err.Error()))
|
|
return
|
|
}
|
|
apnName := q.Get("apnName")
|
|
operType := q.Get("operType")
|
|
if apnName == "" || (operType != "0" && operType != "1") {
|
|
writeOneLink(w, paramErr("apnName 与 operType 必填"))
|
|
return
|
|
}
|
|
orderNum := "APN" + time.Now().Format("20060102150405") + randomHex(3)
|
|
var result map[string]string
|
|
err = s.store.SaveMutation(func(store *Store) error {
|
|
idx := findCardIndex(*store, idKey, idVal)
|
|
if idx < 0 {
|
|
return fmt.Errorf("查询不到信息")
|
|
}
|
|
card := &store.Cards[idx]
|
|
status := "0"
|
|
if operType == "0" {
|
|
status = "1"
|
|
}
|
|
found := false
|
|
for i := range card.APNs {
|
|
if strings.EqualFold(card.APNs[i].Name, apnName) {
|
|
card.APNs[i].Status = status
|
|
found = true
|
|
}
|
|
}
|
|
if !found {
|
|
card.APNs = append(card.APNs, APN{Name: apnName, Status: status})
|
|
}
|
|
now := time.Now().Format("2006-01-02 15:04:05")
|
|
store.Orders = append(store.Orders, Order{
|
|
OrderNum: orderNum, MSISDN: card.MSISDN, ICCID: card.ICCID, IMSI: card.IMSI,
|
|
Type: "sim-apn-function", OperType: operType, APNName: apnName, Status: "completed", CreatedAt: now,
|
|
})
|
|
result = map[string]string{idKey: idVal, "orderNum": orderNum}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
writeOneLink(w, paramErr(err.Error()))
|
|
return
|
|
}
|
|
writeOneLink(w, okResp(result))
|
|
}
|
|
|
|
func okResp(item interface{}) Response {
|
|
return Response{Status: "0", Message: "正确", Result: []interface{}{item}}
|
|
}
|
|
|
|
func paramErr(message string) Response {
|
|
if strings.Contains(message, "查询不到") {
|
|
return Response{Status: "14008", Message: "查询不到信息", Result: []interface{}{}}
|
|
}
|
|
return Response{Status: "12001", Message: message, Result: []interface{}{}}
|
|
}
|
|
|
|
func writeOneLink(w http.ResponseWriter, resp Response) {
|
|
writeJSON(w, http.StatusOK, resp)
|
|
}
|
|
|
|
func writeJSON(w http.ResponseWriter, code int, v interface{}) {
|
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
|
w.WriteHeader(code)
|
|
_ = json.NewEncoder(w).Encode(v)
|
|
}
|
|
|
|
func singleIdentifier(q mapLike) (string, string, error) {
|
|
found := [][2]string{}
|
|
for _, key := range []string{"msisdn", "iccid", "imsi"} {
|
|
if v := q.Get(key); v != "" {
|
|
found = append(found, [2]string{key, v})
|
|
}
|
|
}
|
|
if len(found) != 1 {
|
|
return "", "", fmt.Errorf("msisdn、iccid、imsi 必须有且只有一项")
|
|
}
|
|
return found[0][0], found[0][1], nil
|
|
}
|
|
|
|
type mapLike interface {
|
|
Get(string) string
|
|
}
|
|
|
|
func findSingleCard(r *http.Request, st Store) (SIMCard, error) {
|
|
key, value, err := singleIdentifier(r.URL.Query())
|
|
if err != nil {
|
|
return SIMCard{}, err
|
|
}
|
|
for _, card := range st.Cards {
|
|
if matches(card, key, value) {
|
|
return card, nil
|
|
}
|
|
}
|
|
return SIMCard{}, fmt.Errorf("查询不到信息")
|
|
}
|
|
|
|
func findBatchCards(r *http.Request, st Store) ([]SIMCard, string, error) {
|
|
q := r.URL.Query()
|
|
keys := []string{"msisdns", "iccids", "imsis"}
|
|
foundKey := ""
|
|
foundValue := ""
|
|
for _, key := range keys {
|
|
if v := q.Get(key); v != "" {
|
|
if foundKey != "" {
|
|
return nil, "", fmt.Errorf("msisdns、iccids、imsis 必须有且只有一项")
|
|
}
|
|
foundKey = key
|
|
foundValue = v
|
|
}
|
|
}
|
|
if foundKey == "" {
|
|
return nil, "", fmt.Errorf("msisdns、iccids、imsis 必须有且只有一项")
|
|
}
|
|
singular := strings.TrimSuffix(foundKey, "s")
|
|
ids := strings.Split(foundValue, "_")
|
|
var cards []SIMCard
|
|
for _, id := range ids {
|
|
id = strings.TrimSpace(id)
|
|
for _, card := range st.Cards {
|
|
if matches(card, singular, id) {
|
|
cards = append(cards, card)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if len(cards) == 0 {
|
|
return nil, "", fmt.Errorf("查询不到信息")
|
|
}
|
|
return cards, singular, nil
|
|
}
|
|
|
|
func findCardIndex(st Store, key, value string) int {
|
|
for i, card := range st.Cards {
|
|
if matches(card, key, value) {
|
|
return i
|
|
}
|
|
}
|
|
return -1
|
|
}
|
|
|
|
func matches(card SIMCard, key, value string) bool {
|
|
switch key {
|
|
case "msisdn":
|
|
return card.MSISDN == value
|
|
case "iccid":
|
|
return card.ICCID == value
|
|
case "imsi":
|
|
return card.IMSI == value
|
|
}
|
|
return false
|
|
}
|
|
|
|
func apnUseAmountList(card SIMCard) []map[string]interface{} {
|
|
out := make([]map[string]interface{}, 0, len(card.APNs))
|
|
for _, apn := range card.APNs {
|
|
out = append(out, map[string]interface{}{
|
|
"apnName": apn.Name,
|
|
"apnUseAmount": fmtAmount(apn.UsageBytes / 1024),
|
|
"pccCodeUseAmountList": []map[string]string{{
|
|
"pccCode": "default",
|
|
"pccCodeUseAmount": fmtAmount(apn.UsageBytes / 1024),
|
|
}},
|
|
})
|
|
}
|
|
return out
|
|
}
|
|
|
|
func batchUsageItem(card SIMCard, key string, amount float64) map[string]interface{} {
|
|
item := map[string]interface{}{
|
|
key: identifierValue(card, key),
|
|
"dataAmount": fmtAmount(amount),
|
|
}
|
|
if amount > 0 {
|
|
item["apnDataAmountList"] = []map[string]string{
|
|
{"apnName": "CMIOT", "apnDataAmount": fmtAmount(amount * 0.7)},
|
|
{"apnName": "CMMTM", "apnDataAmount": fmtAmount(amount * 0.3)},
|
|
}
|
|
}
|
|
return item
|
|
}
|
|
|
|
func identifierValue(card SIMCard, key string) string {
|
|
switch key {
|
|
case "iccid":
|
|
return card.ICCID
|
|
case "imsi":
|
|
return card.IMSI
|
|
default:
|
|
return card.MSISDN
|
|
}
|
|
}
|
|
|
|
func fmtAmount(v float64) string {
|
|
if math.Abs(v-math.Round(v)) < 0.001 {
|
|
return strconv.FormatInt(int64(math.Round(v)), 10)
|
|
}
|
|
return fmt.Sprintf("%.2f", v)
|
|
}
|
|
|
|
func statusByOperType(operType, current string) string {
|
|
switch operType {
|
|
case "0":
|
|
return "4"
|
|
case "1", "2", "5", "6":
|
|
return "2"
|
|
case "3", "8":
|
|
return "7"
|
|
case "4", "9":
|
|
return "1"
|
|
default:
|
|
return current
|
|
}
|
|
}
|