chore: 并入 iotlcm-onelink-mock 历史到 onelink-mock/(原提交 f398595)

This commit is contained in:
MiFaZhan
2026-11-02 13:28:02 +08:00
64 changed files with 10585 additions and 0 deletions
+612
View File
@@ -0,0 +1,612 @@
package service
import (
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"strconv"
"strings"
"time"
)
const (
chanSuccessCode = 0
chanErrorCode = 9999
)
type chanSimKey struct {
ChanKey string `json:"chanKey"`
ICCID string `json:"iccid"`
IMSI string `json:"imsi"`
MSISDN string `json:"msisdn"`
}
type chanRequest struct {
SimKeys []chanSimKey `json:"simKeys"`
ReqData json.RawMessage `json:"reqData"`
}
type chanResponse[T any] struct {
Code int `json:"code"`
Message string `json:"message"`
SimKey chanSimKey `json:"simKey"`
ResData T `json:"resData"`
}
type chanBasicInfo struct {
ActiveDate string `json:"activeDate"`
OpenDate string `json:"openDate"`
Remark string `json:"remark"`
}
type chanSession struct {
ApnID string `json:"apnId"`
Rat string `json:"rat"`
IPv4 string `json:"ipv4"`
IPv6 string `json:"ipv6"`
IPv6Prefix string `json:"ipv6Prefix"`
Status string `json:"status"`
CreateTime string `json:"createTime"`
}
type chanCardStatus struct {
CardStatus int `json:"cardStatus"`
LastChangeDate string `json:"lastChangeDate"`
}
type chanIMEI struct {
IMEI string `json:"imei"`
}
type chanUsage struct {
DataUsage float64 `json:"dataUsage"`
}
type chanMonthlyUsage struct {
DeadLine string `json:"deadLine"`
DataAmount float64 `json:"dataAmount"`
}
type chanDailyUsage struct {
QueryDate string `json:"queryDate"`
DataAmount float64 `json:"dataAmount"`
}
type chanAPN struct {
APNName string `json:"apnName"`
Status string `json:"status"`
}
type chanAPNList struct {
APNList []chanAPN `json:"apnList"`
}
type chanOrder struct {
OrderNum string `json:"orderNum"`
}
type chanBindStatus struct {
BindStatus string `json:"bindStatus"`
}
type chanDeviceStatus struct {
DeviceStatus string `json:"deviceStatus"`
}
type chanRegionLimitStatus struct {
RegionLimitStatus string `json:"regionLimitStatus"`
}
type chanReqData struct {
QueryDate string `json:"queryDate"`
OperType string `json:"operType"`
APNName string `json:"apnName"`
TestType *int `json:"testType"`
}
func (s *Service) chanAPI(w http.ResponseWriter, r *http.Request) {
path := strings.TrimPrefix(r.URL.Path, "/sim/")
switch path {
case "query/sim-basic-info":
s.chanQueryBasicInfo(w, r)
case "query/sim-session":
s.chanQuerySession(w, r)
case "query/sim-status", "query/sim-status-batch":
s.chanQueryStatus(w, r)
case "query/sim-card-bind-status":
s.chanQueryBindStatus(w, r)
case "query/sim-device-status":
s.chanQueryDeviceStatus(w, r)
case "query/sim-region-limit-status":
s.chanQueryRegionLimitStatus(w, r)
case "query/sim-imei":
s.chanQueryIMEI(w, r)
case "query/sim-datausage":
s.chanQueryDataUsage(w, r)
case "query/sim-datausage-monthly", "query/sim-datausage-monthly-batch":
s.chanQueryMonthly(w, r)
case "query/sim-datausage-daily", "query/sim-datausage-daily-batch":
s.chanQueryDaily(w, r)
case "query/sim-apn-info":
s.chanQueryAPN(w, r)
case "change/sim-status":
s.chanChangeStatus(w, r)
case "change/sim-apn":
s.chanChangeAPN(w, r)
default:
writeJSON(w, http.StatusNotFound, map[string]string{"message": "unknown sim endpoint"})
}
}
func parseChanRequest(r *http.Request) (chanRequest, error) {
var req chanRequest
if r.Method != http.MethodGet && r.Body != nil {
body, err := io.ReadAll(io.LimitReader(r.Body, 2<<20))
if err != nil {
return req, err
}
if len(strings.TrimSpace(string(body))) > 0 {
if err := json.Unmarshal(body, &req); err != nil {
return req, fmt.Errorf("invalid JSON body: %w", err)
}
}
}
if len(req.SimKeys) == 0 {
req.SimKeys = simKeysFromQuery(r.URL.Query())
}
if len(req.ReqData) == 0 {
data := chanReqData{
QueryDate: r.URL.Query().Get("queryDate"),
OperType: r.URL.Query().Get("operType"),
APNName: r.URL.Query().Get("apnName"),
}
if value := r.URL.Query().Get("testType"); value != "" {
if parsed, err := strconv.Atoi(value); err == nil {
data.TestType = &parsed
}
}
if data.QueryDate != "" || data.OperType != "" || data.APNName != "" || data.TestType != nil {
req.ReqData, _ = json.Marshal(data)
}
}
return req, nil
}
func simKeysFromQuery(q url.Values) []chanSimKey {
if raw := q.Get("simKeys"); raw != "" {
var keys []chanSimKey
if json.Unmarshal([]byte(raw), &keys) == nil {
return keys
}
}
var values []chanSimKey
for _, item := range []struct {
key string
name string
}{
{"iccids", "iccid"}, {"imsis", "imsi"}, {"msisdns", "msisdn"},
} {
for _, value := range splitIDs(q.Get(item.key)) {
key := chanSimKey{}
switch item.name {
case "iccid":
key.ICCID = value
case "imsi":
key.IMSI = value
case "msisdn":
key.MSISDN = value
}
values = append(values, key)
}
}
if len(values) == 0 {
key := chanSimKey{ICCID: q.Get("iccid"), IMSI: q.Get("imsi"), MSISDN: q.Get("msisdn")}
if key.ICCID != "" || key.IMSI != "" || key.MSISDN != "" {
values = append(values, key)
}
}
return values
}
func splitIDs(raw string) []string {
if raw == "" {
return nil
}
raw = strings.ReplaceAll(raw, ",", "_")
parts := strings.Split(raw, "_")
values := make([]string, 0, len(parts))
for _, part := range parts {
if value := strings.TrimSpace(part); value != "" {
values = append(values, value)
}
}
return values
}
func decodeChanReqData(req chanRequest) chanReqData {
var data chanReqData
_ = json.Unmarshal(req.ReqData, &data)
return data
}
func chanSuccess[T any](key chanSimKey, data T) chanResponse[T] {
return chanResponse[T]{Code: chanSuccessCode, Message: "success", SimKey: key, ResData: data}
}
func chanFailure[T any](key chanSimKey, message string) chanResponse[T] {
return chanResponse[T]{Code: chanErrorCode, Message: message, SimKey: key, ResData: *new(T)}
}
func writeChanResponses[T any](w http.ResponseWriter, responses []chanResponse[T]) {
writeJSON(w, http.StatusOK, responses)
}
func (s *Service) chanCards(r *http.Request) (chanRequest, Store, error) {
req, err := parseChanRequest(r)
if err != nil {
return req, Store{}, err
}
return req, s.store.Snapshot(), nil
}
func findChanCard(st Store, key chanSimKey) (SIMCard, bool) {
if key.ICCID != "" {
for _, card := range st.Cards {
if card.ICCID == key.ICCID {
return card, true
}
}
}
if key.IMSI != "" {
for _, card := range st.Cards {
if card.IMSI == key.IMSI {
return card, true
}
}
}
if key.MSISDN != "" {
for _, card := range st.Cards {
if card.MSISDN == key.MSISDN {
return card, true
}
}
}
return SIMCard{}, false
}
func chanRequestError[T any](w http.ResponseWriter, err error) {
writeJSON(w, http.StatusBadRequest, []chanResponse[T]{{Code: chanErrorCode, Message: err.Error()}})
}
func (s *Service) chanQueryBasicInfo(w http.ResponseWriter, r *http.Request) {
req, st, err := s.chanCards(r)
if err != nil {
chanRequestError[chanBasicInfo](w, err)
return
}
responses := make([]chanResponse[chanBasicInfo], 0, len(req.SimKeys))
for _, key := range req.SimKeys {
card, ok := findChanCard(st, key)
if !ok {
responses = append(responses, chanFailure[chanBasicInfo](key, "card not found"))
continue
}
responses = append(responses, chanSuccess(key, chanBasicInfo{ActiveDate: card.ActiveDate, OpenDate: card.OpenDate, Remark: card.Remark}))
}
writeChanResponses(w, responses)
}
func (s *Service) chanQuerySession(w http.ResponseWriter, r *http.Request) {
req, st, err := s.chanCards(r)
if err != nil {
chanRequestError[chanSession](w, err)
return
}
responses := make([]chanResponse[chanSession], 0, len(req.SimKeys))
for _, key := range req.SimKeys {
card, ok := findChanCard(st, key)
if !ok {
responses = append(responses, chanFailure[chanSession](key, "card not found"))
continue
}
responses = append(responses, chanSuccess(key, chanSession{ApnID: primaryAPN(card).Name, Rat: card.RAT, IPv4: card.IP, IPv6: card.IPv6, IPv6Prefix: card.IPv6Prefix, Status: chanSessionStatus(card.SessionStatus), CreateTime: card.LastChangeDate}))
}
writeChanResponses(w, responses)
}
func (s *Service) chanQueryStatus(w http.ResponseWriter, r *http.Request) {
req, st, err := s.chanCards(r)
if err != nil {
chanRequestError[chanCardStatus](w, err)
return
}
responses := make([]chanResponse[chanCardStatus], 0, len(req.SimKeys))
for _, key := range req.SimKeys {
card, ok := findChanCard(st, key)
if !ok {
responses = append(responses, chanFailure[chanCardStatus](key, "card not found"))
continue
}
status, _ := strconv.Atoi(card.CardStatus)
responses = append(responses, chanSuccess(key, chanCardStatus{CardStatus: status, LastChangeDate: card.LastChangeDate}))
}
writeChanResponses(w, responses)
}
func (s *Service) chanQueryBindStatus(w http.ResponseWriter, r *http.Request) {
req, st, err := s.chanCards(r)
if err != nil {
chanRequestError[chanBindStatus](w, err)
return
}
responses := make([]chanResponse[chanBindStatus], 0, len(req.SimKeys))
for _, key := range req.SimKeys {
card, ok := findChanCard(st, key)
if !ok {
responses = append(responses, chanFailure[chanBindStatus](key, "card not found"))
continue
}
responses = append(responses, chanSuccess(key, chanBindStatus{BindStatus: card.CardBindStatus}))
}
writeChanResponses(w, responses)
}
func (s *Service) chanQueryDeviceStatus(w http.ResponseWriter, r *http.Request) {
req, st, err := s.chanCards(r)
if err != nil {
chanRequestError[chanDeviceStatus](w, err)
return
}
responses := make([]chanResponse[chanDeviceStatus], 0, len(req.SimKeys))
for _, key := range req.SimKeys {
card, ok := findChanCard(st, key)
if !ok {
responses = append(responses, chanFailure[chanDeviceStatus](key, "card not found"))
continue
}
responses = append(responses, chanSuccess(key, chanDeviceStatus{DeviceStatus: chanSessionStatus(card.OnOffStatus)}))
}
writeChanResponses(w, responses)
}
func (s *Service) chanQueryRegionLimitStatus(w http.ResponseWriter, r *http.Request) {
req, st, err := s.chanCards(r)
if err != nil {
chanRequestError[chanRegionLimitStatus](w, err)
return
}
responses := make([]chanResponse[chanRegionLimitStatus], 0, len(req.SimKeys))
for _, key := range req.SimKeys {
card, ok := findChanCard(st, key)
if !ok {
responses = append(responses, chanFailure[chanRegionLimitStatus](key, "card not found"))
continue
}
responses = append(responses, chanSuccess(key, chanRegionLimitStatus{RegionLimitStatus: card.RegionLimitStatus}))
}
writeChanResponses(w, responses)
}
func (s *Service) chanQueryIMEI(w http.ResponseWriter, r *http.Request) {
req, st, err := s.chanCards(r)
if err != nil {
chanRequestError[chanIMEI](w, err)
return
}
responses := make([]chanResponse[chanIMEI], 0, len(req.SimKeys))
for _, key := range req.SimKeys {
card, ok := findChanCard(st, key)
if !ok {
responses = append(responses, chanFailure[chanIMEI](key, "card not found"))
continue
}
responses = append(responses, chanSuccess(key, chanIMEI{IMEI: card.IMEI}))
}
writeChanResponses(w, responses)
}
func (s *Service) chanQueryDataUsage(w http.ResponseWriter, r *http.Request) {
req, st, err := s.chanCards(r)
if err != nil {
chanRequestError[chanUsage](w, err)
return
}
responses := make([]chanResponse[chanUsage], 0, len(req.SimKeys))
for _, key := range req.SimKeys {
card, ok := findChanCard(st, key)
if !ok {
responses = append(responses, chanFailure[chanUsage](key, "card not found"))
continue
}
responses = append(responses, chanSuccess(key, chanUsage{DataUsage: card.MonthUsageKB / 1024}))
}
writeChanResponses(w, responses)
}
func (s *Service) chanQueryMonthly(w http.ResponseWriter, r *http.Request) {
req, st, err := s.chanCards(r)
if err != nil {
chanRequestError[chanMonthlyUsage](w, err)
return
}
data := decodeChanReqData(req)
responses := make([]chanResponse[chanMonthlyUsage], 0, len(req.SimKeys))
for _, key := range req.SimKeys {
card, ok := findChanCard(st, key)
if !ok {
responses = append(responses, chanFailure[chanMonthlyUsage](key, "card not found"))
continue
}
amount := card.MonthUsageKB
if data.QueryDate != "" && len(data.QueryDate) == 6 && data.QueryDate != time.Now().Format("200601") {
amount = card.MonthlyBaseKB
}
responses = append(responses, chanSuccess(key, chanMonthlyUsage{DeadLine: time.Now().AddDate(0, 0, -1).Format("20060102"), DataAmount: amount / 1024}))
}
writeChanResponses(w, responses)
}
func (s *Service) chanQueryDaily(w http.ResponseWriter, r *http.Request) {
req, st, err := s.chanCards(r)
if err != nil {
chanRequestError[chanDailyUsage](w, err)
return
}
data := decodeChanReqData(req)
queryDate := data.QueryDate
if queryDate == "" {
queryDate = r.URL.Query().Get("queryDate")
}
if queryDate == "" {
queryDate = time.Now().Format("20060102")
}
responses := make([]chanResponse[chanDailyUsage], 0, len(req.SimKeys))
for _, key := range req.SimKeys {
card, ok := findChanCard(st, key)
if !ok {
responses = append(responses, chanFailure[chanDailyUsage](key, "card not found"))
continue
}
responses = append(responses, chanSuccess(key, chanDailyUsage{QueryDate: queryDate, DataAmount: card.DailyBaseKB / 1024}))
}
writeChanResponses(w, responses)
}
func (s *Service) chanQueryAPN(w http.ResponseWriter, r *http.Request) {
req, st, err := s.chanCards(r)
if err != nil {
chanRequestError[chanAPNList](w, err)
return
}
responses := make([]chanResponse[chanAPNList], 0, len(req.SimKeys))
for _, key := range req.SimKeys {
card, ok := findChanCard(st, key)
if !ok {
responses = append(responses, chanFailure[chanAPNList](key, "card not found"))
continue
}
apns := make([]chanAPN, 0, len(card.APNs))
for _, apn := range card.APNs {
apns = append(apns, chanAPN{APNName: apn.Name, Status: apn.Status})
}
responses = append(responses, chanSuccess(key, chanAPNList{APNList: apns}))
}
writeChanResponses(w, responses)
}
func (s *Service) chanChangeStatus(w http.ResponseWriter, r *http.Request) {
req, err := parseChanRequest(r)
if err != nil {
chanRequestError[chanOrder](w, err)
return
}
data := decodeChanReqData(req)
if data.OperType != "0" && data.OperType != "1" {
responses := make([]chanResponse[chanOrder], 0, len(req.SimKeys))
for _, key := range req.SimKeys {
responses = append(responses, chanFailure[chanOrder](key, "unsupported operType, expected 0(stop) or 1(resume)"))
}
writeChanResponses(w, responses)
return
}
responses := make([]chanResponse[chanOrder], 0, len(req.SimKeys))
err = s.store.SaveMutation(func(st *Store) error {
for _, key := range req.SimKeys {
idx := findChanCardIndex(*st, key)
if idx < 0 {
responses = append(responses, chanFailure[chanOrder](key, "card not found"))
continue
}
card := &st.Cards[idx]
if data.OperType == "0" {
card.CardStatus = "4"
} else {
card.CardStatus = "2"
}
card.LastChangeDate = time.Now().Format("2006-01-02 15:04:05")
orderNum := "ORD" + time.Now().Format("20060102150405") + randomHex(3)
st.Orders = append(st.Orders, Order{OrderNum: orderNum, MSISDN: card.MSISDN, ICCID: card.ICCID, IMSI: card.IMSI, Type: "sim-status", OperType: data.OperType, Status: "completed", CreatedAt: card.LastChangeDate})
responses = append(responses, chanSuccess(key, chanOrder{OrderNum: orderNum}))
}
return nil
})
if err != nil {
chanRequestError[chanOrder](w, err)
return
}
writeChanResponses(w, responses)
}
func (s *Service) chanChangeAPN(w http.ResponseWriter, r *http.Request) {
req, err := parseChanRequest(r)
if err != nil {
chanRequestError[chanOrder](w, err)
return
}
data := decodeChanReqData(req)
if data.APNName == "" || (data.OperType != "0" && data.OperType != "1") {
responses := make([]chanResponse[chanOrder], 0, len(req.SimKeys))
for _, key := range req.SimKeys {
responses = append(responses, chanFailure[chanOrder](key, "apnName and operType are required"))
}
writeChanResponses(w, responses)
return
}
responses := make([]chanResponse[chanOrder], 0, len(req.SimKeys))
err = s.store.SaveMutation(func(st *Store) error {
for _, key := range req.SimKeys {
idx := findChanCardIndex(*st, key)
if idx < 0 {
responses = append(responses, chanFailure[chanOrder](key, "card not found"))
continue
}
card := &st.Cards[idx]
status := "0"
if data.OperType == "0" {
status = "1"
}
found := false
for i := range card.APNs {
if strings.EqualFold(card.APNs[i].Name, data.APNName) {
card.APNs[i].Status = status
found = true
}
}
if !found {
card.APNs = append(card.APNs, APN{Name: data.APNName, Status: status})
}
orderNum := "APN" + time.Now().Format("20060102150405") + randomHex(3)
now := time.Now().Format("2006-01-02 15:04:05")
st.Orders = append(st.Orders, Order{OrderNum: orderNum, MSISDN: card.MSISDN, ICCID: card.ICCID, IMSI: card.IMSI, Type: "sim-apn-function", OperType: data.OperType, APNName: data.APNName, Status: "completed", CreatedAt: now})
responses = append(responses, chanSuccess(key, chanOrder{OrderNum: orderNum}))
}
return nil
})
if err != nil {
chanRequestError[chanOrder](w, err)
return
}
writeChanResponses(w, responses)
}
func findChanCardIndex(st Store, key chanSimKey) int {
for i, card := range st.Cards {
if (key.ICCID != "" && card.ICCID == key.ICCID) || (key.IMSI != "" && card.IMSI == key.IMSI) || (key.MSISDN != "" && card.MSISDN == key.MSISDN) {
return i
}
}
return -1
}
func chanSessionStatus(status string) string {
switch strings.ToUpper(strings.TrimSpace(status)) {
case "01", "1", "0", "ONLINE":
return "0"
case "00", "-1", "OFFLINE":
return "-1"
default:
return "-2"
}
}