diff --git a/go.mod b/go.mod index 5b1fae31..6a73a069 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.24 require ( github.com/dlclark/regexp2 v1.11.4 + github.com/expr-lang/expr v1.17.6 github.com/gin-gonic/gin v1.10.1 github.com/go-ldap/ldap/v3 v3.4.11 github.com/go-resty/resty/v2 v2.14.0 @@ -24,13 +25,14 @@ require ( github.com/prometheus-community/pro-bing v0.7.0 github.com/redis/go-redis/v9 v9.12.0 github.com/robfig/cron/v3 v3.0.1 - github.com/shirou/gopsutil/v4 v4.24.12 + github.com/shirou/gopsutil/v4 v4.25.6 github.com/slayercat/GoSNMPServer v0.5.2 - github.com/spf13/pflag v1.0.5 - github.com/spf13/viper v1.19.0 + github.com/spf13/pflag v1.0.6 + github.com/spf13/viper v1.20.1 github.com/swaggo/files v1.0.1 github.com/swaggo/gin-swagger v1.6.0 github.com/swaggo/swag v1.16.4 + github.com/tsmask/go-oam v1.0.9 github.com/wneessen/go-mail v0.6.2 github.com/xuri/excelize/v2 v2.9.0 github.com/xuri/xgen v0.0.0-20240722131518-d0691b701898 @@ -56,10 +58,11 @@ require ( github.com/chenjiandongx/ginprom v0.0.0-20210617023641-6c809602c38a github.com/cloudwego/base64x v0.1.4 // indirect github.com/cloudwego/iasm v0.2.0 // indirect + github.com/creack/pty v1.1.24 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect - github.com/ebitengine/purego v0.8.1 // indirect + github.com/ebitengine/purego v0.8.4 // indirect github.com/fastly/go-utils v0.0.0-20180712184237-d95a45783239 // indirect - github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/fsnotify/fsnotify v1.8.0 // indirect github.com/gabriel-vasile/mimetype v1.4.5 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect @@ -72,11 +75,11 @@ require ( github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.22.0 + github.com/go-viper/mapstructure/v2 v2.2.1 // indirect github.com/goccy/go-json v0.10.3 // indirect github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/hashicorp/hcl v1.0.0 // indirect github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect @@ -91,11 +94,9 @@ require ( github.com/lestrrat/go-envload v0.0.0-20180220120943-6ed08b54a570 // indirect github.com/lestrrat/go-strftime v0.0.0-20180220042222-ba3bf9c1d042 // indirect github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae // indirect - github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.9.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/metaleap/go-util v0.0.0-20180330192724-a09253046f73 // indirect - github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect @@ -103,7 +104,7 @@ require ( github.com/onsi/ginkgo v1.16.5 // indirect github.com/onsi/gomega v1.21.1 // indirect github.com/orcaman/concurrent-map/v2 v2.0.1 // indirect - github.com/pelletier/go-toml/v2 v2.2.2 // indirect + github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/prometheus/client_golang v1.20.0 @@ -112,14 +113,13 @@ require ( github.com/prometheus/procfs v0.15.1 // indirect github.com/richardlehane/mscfb v1.0.4 // indirect github.com/richardlehane/msoleps v1.0.4 // indirect - github.com/sagikazarmark/locafero v0.6.0 // indirect - github.com/sagikazarmark/slog-shim v0.1.0 // indirect; indirect // indirect + github.com/sagikazarmark/locafero v0.7.0 // indirect github.com/shirou/gopsutil/v3 v3.23.11 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/sirupsen/logrus v1.4.2 // indirect github.com/sourcegraph/conc v0.3.0 // indirect; indirect // indirect - github.com/spf13/afero v1.11.0 // indirect; indirect // indirect - github.com/spf13/cast v1.7.0 // indirect; indirect // indirect + github.com/spf13/afero v1.12.0 // indirect; indirect // indirect + github.com/spf13/cast v1.7.1 // indirect; indirect // indirect github.com/subosito/gotenv v1.6.0 // indirect; indirect // indirect github.com/syndtr/goleveldb v1.0.0 // indirect; indirect // indirect github.com/tebeka/strftime v0.1.5 // indirect @@ -135,12 +135,11 @@ require ( golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa // indirect golang.org/x/image v0.19.0 // indirect golang.org/x/net v0.38.0 // indirect - golang.org/x/sync v0.13.0 // indirect + golang.org/x/sync v0.14.0 // indirect golang.org/x/sys v0.31.0 // indirect golang.org/x/tools v0.28.0 // indirect golang.org/x/tools/cmd/guru v0.1.1-deprecated // indirect - google.golang.org/protobuf v1.34.2 // indirect + google.golang.org/protobuf v1.36.1 // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect - gopkg.in/ini.v1 v1.67.0 // indirect xorm.io/builder v0.3.13 // indirect ) diff --git a/go.sum b/go.sum index d8cef023..e60e7ad5 100644 --- a/go.sum +++ b/go.sum @@ -29,26 +29,29 @@ github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJ github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= +github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 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/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo= github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/ebitengine/purego v0.8.1 h1:sdRKd6plj7KYW33EH5As6YKfe8m9zbN9JMrOjNVF/BE= -github.com/ebitengine/purego v0.8.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw= +github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/expr-lang/expr v1.17.6 h1:1h6i8ONk9cexhDmowO/A64VPxHScu7qfSl2k8OlINec= +github.com/expr-lang/expr v1.17.6/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4= github.com/fastly/go-utils v0.0.0-20180712184237-d95a45783239 h1:Ghm4eQYC0nEPnSJdVkTrXpu9KtoVCSo1hg7mtI7G9KU= github.com/fastly/go-utils v0.0.0-20180712184237-d95a45783239/go.mod h1:Gdwt2ce0yfBxPvZrHkprdPPTTS3N5rwmLE8T22KBXlw= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= -github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= +github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/gabriel-vasile/mimetype v1.4.5 h1:J7wGKdGu33ocBOhGy0z653k/lFKLFDPJMG8Gql0kxn4= github.com/gabriel-vasile/mimetype v1.4.5/go.mod h1:ibHel+/kbxn9x2407k1izTA1S81ku1z/DlgOW2QE0M4= github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4= @@ -88,6 +91,8 @@ github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9 github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= +github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= @@ -110,8 +115,9 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +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/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -127,8 +133,6 @@ github.com/gosnmp/gosnmp v1.38.0 h1:I5ZOMR8kb0DXAFg/88ACurnuwGwYkXWq3eLpJPHMEYc= github.com/gosnmp/gosnmp v1.38.0/go.mod h1:FE+PEZvKrFz9afP9ii1W3cprXuVZ17ypCcyyfYuu5LY= 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/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= @@ -185,8 +189,6 @@ github.com/linxGnu/gosmpp v0.3.0/go.mod h1:Ba6SULQql3IbF2A5Mtj3DqMKoFbx1pEz/8xyi github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae h1:dIZY4ULFcto4tAFlj1FYZl8ztUZ13bdq+PLY+NOfbyI= github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= -github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= -github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/matoous/go-nanoid/v2 v2.1.0 h1:P64+dmq21hhWdtvZfEAofnvJULaRR1Yib0+PnU669bE= @@ -199,8 +201,6 @@ github.com/metaleap/go-util v0.0.0-20180330192724-a09253046f73 h1:4vKVhAdype/dej github.com/metaleap/go-util v0.0.0-20180330192724-a09253046f73/go.mod h1:l71/5fppWP5A6nqhcxz6wQAYok6pr/vM2+KHIy50/LY= github.com/metaleap/go-xsd v0.0.0-20180330193350-61f7638f502f h1:eeJGcYszuvOpmuJxeq57LaOO8mJurfjpOHJJMfQSD0s= github.com/metaleap/go-xsd v0.0.0-20180330193350-61f7638f502f/go.mod h1:WK3zEKtwVd/v+NM3lh1ZE6MdDfHsdOFFOD5Ezi4Hutg= -github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= -github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -231,15 +231,14 @@ github.com/orcaman/concurrent-map/v2 v2.0.1 h1:jOJ5Pg2w1oeB6PeDurIYf6k9PQ+aTITr/ github.com/orcaman/concurrent-map/v2 v2.0.1/go.mod h1:9Eq3TG2oBe5FirmYWQfYO5iH1q0Jv47PLaNK++uCdOM= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= -github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= -github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= +github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.13.9 h1:4NGkvGudBL7GteO3m6qnaQ4pC0Kvf0onSVc9gR3EWBw= github.com/pkg/sftp v1.13.9/go.mod h1:OBN7bVXdstkFFN/gdnHPUb5TE8eb8G1Rp9wCItqjkkA= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 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/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= @@ -268,14 +267,12 @@ github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzG github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sagikazarmark/locafero v0.6.0 h1:ON7AQg37yzcRPU69mt7gwhFEBwxI6P9T4Qu3N51bwOk= -github.com/sagikazarmark/locafero v0.6.0/go.mod h1:77OmuIc6VTraTXKXIs/uvUxKGUXjE1GbemJYHqdNjX0= -github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= -github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo= +github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= github.com/shirou/gopsutil/v3 v3.23.11 h1:i3jP9NjCPUz7FiZKxlMnODZkdSIp2gnzfrvsu9CuWEQ= github.com/shirou/gopsutil/v3 v3.23.11/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= -github.com/shirou/gopsutil/v4 v4.24.12 h1:qvePBOk20e0IKA1QXrIIU+jmk+zEiYVVx06WjBRlZo4= -github.com/shirou/gopsutil/v4 v4.24.12/go.mod h1:DCtMPAad2XceTeIAbGyVfycbYQNBGk2P8cvDi7/VN9o= +github.com/shirou/gopsutil/v4 v4.25.6 h1:kLysI2JsKorfaFPcYmcJqbzROzsBWEOAtw6A7dIfqXs= +github.com/shirou/gopsutil/v4 v4.25.6/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= @@ -287,19 +284,18 @@ github.com/slayercat/GoSNMPServer v0.5.2 h1:IK2d3kz6JoiYHbAZT5H7hrQQRzAD7rxF0iJZ github.com/slayercat/GoSNMPServer v0.5.2/go.mod h1:6taMSIwudR+7pKRO6dz2U+xoNccZds8eiMVlEN66fXY= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= -github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= -github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= -github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= -github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= -github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= +github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= +github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= +github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= +github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4= +github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= 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.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 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.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= @@ -308,7 +304,6 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= @@ -329,6 +324,8 @@ github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYN github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= github.com/tklauser/numcpus v0.8.0 h1:Mx4Wwe/FjZLeQsK/6kt2EOepwwSl7SmJrK5bV/dXYgY= github.com/tklauser/numcpus v0.8.0/go.mod h1:ZJZlAY+dmR4eut8epnzf0u/VwodKmryxR8txiloSqBE= +github.com/tsmask/go-oam v1.0.9 h1:6g3rpMwSu0x8mzvq4QdMZgbABvMo3ek2D0x18Ecm7uU= +github.com/tsmask/go-oam v1.0.9/go.mod h1:HqFtN0LA9BiR1HWyHO++DY0fyYTl2sKVnRrZzuWy9n4= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= @@ -417,8 +414,8 @@ golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= -golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= +golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -478,8 +475,9 @@ golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= -golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= +golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= @@ -503,8 +501,8 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= +google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -513,8 +511,6 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE= gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw= -gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= -gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/src/modules/network_data/controller/ims.go b/src/modules/network_data/controller/ims.go index 85dd9274..78a0bd6d 100644 --- a/src/modules/network_data/controller/ims.go +++ b/src/modules/network_data/controller/ims.go @@ -28,7 +28,7 @@ import ( var NewIMS = &IMSController{ neInfoService: neService.NewNeInfo, cdrEventService: neDataService.NewCDREventIMS, - kpiReportService: neDataService.NewPerfKPI, + kpiReportService: neDataService.NewKpiReport, } // 网元IMS @@ -37,7 +37,7 @@ var NewIMS = &IMSController{ type IMSController struct { neInfoService *neService.NeInfo // 网元信息服务 cdrEventService *neDataService.CDREventIMS // CDR会话事件服务 - kpiReportService *neDataService.PerfKPI // 统计信息服务 + kpiReportService *neDataService.KpiReport // 统计信息服务 } // CDR会话列表 diff --git a/src/modules/network_data/controller/upf.go b/src/modules/network_data/controller/upf.go index c2c2ec1a..8067e057 100644 --- a/src/modules/network_data/controller/upf.go +++ b/src/modules/network_data/controller/upf.go @@ -12,15 +12,15 @@ import ( // 实例化控制层 UPFController 结构体 var NewUPF = &UPFController{ neInfoService: neService.NewNeInfo, - perfKPIService: neDataService.NewPerfKPI, + perfKPIService: neDataService.NewKpiReport, } // 网元UPF // // PATH /upf type UPFController struct { - neInfoService *neService.NeInfo // 网元信息服务 - perfKPIService *neDataService.PerfKPI // 统计信息服务 + neInfoService *neService.NeInfo // 网元信息服务 + perfKPIService *neDataService.KpiReport // 统计信息服务 } // 总流量数 N3上行 N6下行 diff --git a/src/modules/network_data/model/alarm_event.go b/src/modules/network_data/model/alarm_event.go new file mode 100644 index 00000000..3a3ab72e --- /dev/null +++ b/src/modules/network_data/model/alarm_event.go @@ -0,0 +1,46 @@ +package model + +import "time" + +// AlarmEvent 告警_事件记录表 +type AlarmEvent struct { + ID int64 `json:"id" gorm:"column:id;primaryKey;autoIncrement"` + NeType string `json:"neType" gorm:"column:ne_type"` // 网元类型 + NeId string `json:"neId" gorm:"column:ne_id"` // 网元ID + AlarmSeq string `json:"alarmSeq" gorm:"column:alarm_seq"` // 告警序号 同网元类型连续递增 + AlarmId string `json:"alarmId" gorm:"column:alarm_id"` // 告警ID + AlarmTitle string `json:"alarmTitle" gorm:"column:alarm_title"` // 告警标题 + AlarmCode string `json:"alarmCode" gorm:"column:alarm_code"` // 告警状态码 + EventTime time.Time `json:"eventTime" gorm:"column:event_time"` // 事件产生时间 + ObjectUid string `json:"objectUid" gorm:"column:object_uid"` // 对象ID + ObjectName string `json:"objectName" gorm:"column:object_name"` // 对象名称 + ObjectType string `json:"objectType" gorm:"column:object_type"` // 对象类型 + LocationInfo string `json:"locationInfo" gorm:"column:location_info"` // 告警定位信息 + AlarmStatus string `json:"alarmStatus" gorm:"column:alarm_status"` // 告警状态 + SpecificProblem string `json:"specificProblem" gorm:"column:specific_problem"` // 告警问题原因 + SpecificProblemId string `json:"specificProblemId" gorm:"column:specific_problem_id"` // 告警问题原因ID + AddInfo string `json:"addInfo" gorm:"column:add_info"` // 告警辅助信息 + ClearType string `json:"clearType" gorm:"column:clear_type"` // 清除状态 + ClearTime time.Time `json:"clearTime" gorm:"column:clear_time"` // 清除时间 + ClearUser string `json:"clearUser" gorm:"column:clear_user"` // 清除用户 + Timestamp time.Time `json:"timestamp" gorm:"column:timestamp"` // 创建时间 +} + +// TableName 表名称 +func (*AlarmEvent) TableName() string { + return "alarm_event" +} + +// AlarmEventQuery 告警事件数据查询参数结构体 +type AlarmEventQuery struct { + NeType string `json:"neType" form:"neType"` // 网元类型 + NeID string `json:"neId" form:"neId"` // 网元ID + AlarmCode string `json:"alarmCode" form:"alarmCode"` + AlarmStatus string `json:"alarmStatus" form:"alarmStatus" binding:"omitempty,oneof=Clear Active"` // 告警状态 + BeginTime int64 `json:"beginTime" form:"beginTime"` // 开始时间 查event_time + EndTime int64 `json:"endTime" form:"endTime"` + SortField string `json:"sortField" form:"sortField" binding:"omitempty,oneof=event_time id"` // 排序字段,填写结果字段 + SortOrder string `json:"sortOrder" form:"sortOrder" binding:"omitempty,oneof=asc desc"` // 排序升降序,asc desc + PageNum int64 `json:"pageNum" form:"pageNum" binding:"required"` + PageSize int64 `json:"pageSize" form:"pageSize" binding:"required"` +} diff --git a/src/modules/network_data/model/alarm_forward_log.go b/src/modules/network_data/model/alarm_forward_log.go new file mode 100644 index 00000000..1d132de2 --- /dev/null +++ b/src/modules/network_data/model/alarm_forward_log.go @@ -0,0 +1,39 @@ +package model + +import "time" + +// AlarmForwardLog 告警_转发日志记录 +type AlarmForwardLog struct { + ID int64 `json:"id" gorm:"column:id;primaryKey;autoIncrement"` + NeType string `json:"neType" gorm:"column:ne_type"` + NeId string `json:"neId" gorm:"column:ne_id"` + AlarmSeq string `json:"alarmSeq" gorm:"column:alarm_seq"` // 告警序号 连续递增 + AlarmId string `json:"alarmId" gorm:"column:alarm_id"` // 告警ID + AlarmCode string `json:"alarmCode" gorm:"column:alarm_code"` // 告警状态码 + AlarmTitle string `json:"alarmTitle" gorm:"column:alarm_title"` // 告警标题 + AlarmStatus string `json:"alarmStatus" gorm:"column:alarm_status"` // 告警状态 + AlarmType string `json:"alarmType" gorm:"column:alarm_type"` // 告警类型 + OrigSeverity string `json:"origSeverity" gorm:"column:orig_severity"` // 严重程度 + EventTime time.Time `json:"eventTime" gorm:"column:event_time"` // 事件产生时间 + CreatedAt time.Time `json:"createdAt" gorm:"column:created_at"` // 创建时间 + Type string `json:"type" gorm:"column:type"` // 转发方式 SMS/EMAIL/SMSC + Target string `json:"target" gorm:"column:target"` // 发送目标用户 + Result string `json:"result" gorm:"column:result"` // 发送结果 +} + +// TableName 表名称 +func (*AlarmForwardLog) TableName() string { + return "alarm_forward_log" +} + +// AlarmForwardLogQuery 告警转发日志数据查询参数结构体 +type AlarmForwardLogQuery struct { + NeType string `json:"neType" form:"neType"` // 网元类型 + NeID string `json:"neId" form:"neId"` // 网元ID + BeginTime int64 `json:"beginTime" form:"beginTime"` // 开始时间 + EndTime int64 `json:"endTime" form:"endTime"` + SortField string `json:"sortField" form:"sortField" binding:"omitempty,oneof=event_time id"` // 排序字段,填写结果字段 + SortOrder string `json:"sortOrder" form:"sortOrder" binding:"omitempty,oneof=asc desc"` // 排序升降序,asc desc + PageNum int64 `json:"pageNum" form:"pageNum" binding:"required"` + PageSize int64 `json:"pageSize" form:"pageSize" binding:"required"` +} diff --git a/src/modules/network_data/model/alarm_log.go b/src/modules/network_data/model/alarm_log.go new file mode 100644 index 00000000..2b051c91 --- /dev/null +++ b/src/modules/network_data/model/alarm_log.go @@ -0,0 +1,38 @@ +package model + +import "time" + +// AlarmLog 告警_日志记录 +type AlarmLog struct { + ID int64 `json:"id" gorm:"column:id;primaryKey;autoIncrement"` + NeType string `json:"neType" gorm:"column:ne_type"` + NeId string `json:"neId" gorm:"column:ne_id"` + AlarmSeq string `json:"alarmSeq" gorm:"column:alarm_seq"` // 告警序号 连续递增 + AlarmId string `json:"alarmId" gorm:"column:alarm_id"` // 告警ID + AlarmCode string `json:"alarmCode" gorm:"column:alarm_code"` // 告警状态码 + AlarmTitle string `json:"alarmTitle" gorm:"column:alarm_title"` // 告警标题 + AlarmStatus string `json:"alarmStatus" gorm:"column:alarm_status"` // 告警状态 + AlarmType string `json:"alarmType" gorm:"column:alarm_type"` // 告警类型 + OrigSeverity string `json:"origSeverity" gorm:"column:orig_severity"` // 严重程度 + EventTime time.Time `json:"eventTime" gorm:"column:event_time"` // 事件产生时间 + CreatedAt time.Time `json:"createdAt" gorm:"column:created_at"` // 创建时间 +} + +// TableName 表名称 +func (*AlarmLog) TableName() string { + return "alarm_log" +} + +// AlarmLogQuery 告警日志数据查询参数结构体 +type AlarmLogQuery struct { + NeType string `json:"neType" form:"neType"` // 网元类型 + NeID string `json:"neId" form:"neId"` // 网元ID + AlarmStatus string `json:"alarmStatus" form:"alarmStatus" binding:"omitempty,oneof=Clear Active"` // 告警状态 + OrigSeverity string `json:"origSeverity" form:"origSeverity"` // 告警类型 + BeginTime int64 `json:"beginTime" form:"beginTime"` // 开始时间 查event_time + EndTime int64 `json:"endTime" form:"endTime"` + SortField string `json:"sortField" form:"sortField" binding:"omitempty,oneof=event_time id"` // 排序字段,填写结果字段 + SortOrder string `json:"sortOrder" form:"sortOrder" binding:"omitempty,oneof=asc desc"` // 排序升降序,asc desc + PageNum int64 `json:"pageNum" form:"pageNum" binding:"required"` + PageSize int64 `json:"pageSize" form:"pageSize" binding:"required"` +} diff --git a/src/modules/network_data/model/cbc_message.go b/src/modules/network_data/model/cbc_message.go new file mode 100644 index 00000000..37470209 --- /dev/null +++ b/src/modules/network_data/model/cbc_message.go @@ -0,0 +1,112 @@ +package model + +import ( + "database/sql/driver" + "encoding/json" + "errors" + "fmt" + "strconv" + "strings" +) + +type CBCEventStatus int + +// CBCEventStatus CB事件状态枚举 +const ( + CBCEventStatusNull CBCEventStatus = iota // 未知状态 + CBCEventStatusActive + CBCEventStatusInactive +) + +func (status CBCEventStatus) Enum() string { + switch status { + case CBCEventStatusNull: + return "NULL" + case CBCEventStatusActive: + return "ACTIVE" + case CBCEventStatusInactive: + return "INACTIVE" + default: + return "UNKNOWN" + } +} + +func (status CBCEventStatus) String() string { + return fmt.Sprintf("%d", status) +} + +// ParseCBCEventStatus 将字符串转换为 枚举类型 +func ParseCBCEventStatus(s string) CBCEventStatus { + if i, err := strconv.Atoi(s); err == nil { + return CBCEventStatus(i) + } + // 如果转换失败,则按名称匹配(忽略大小写) + switch strings.ToUpper(s) { + case "NULL": + return CBCEventStatusNull + case "ACTIVE": + return CBCEventStatusActive + case "INACTIVE": + return CBCEventStatusInactive + case "": + // 如果字符串为空,则返回未知状态 + return CBCEventStatusNull + default: + // 默认返回未知状态 + return CBCEventStatusNull + } +} + +// CBCMessageQuery 查询条件结构体 +type CBCMessageQuery struct { + NeType string `form:"neType"` // 网元类型 + NeId string `form:"neId"` // 网元ID + EventName string `form:"eventName"` // 事件名称 + Status string `form:"status"` // 消息状态 + StartTime string `form:"startTime"` // 创建时间范围-起始 + EndTime string `form:"endTime"` // 创建时间范围-结束 + PageNum int `form:"pageNum" binding:"required"` + PageSize int `form:"pageSize" binding:"required"` +} + +// @Description CBCMessage CB消息 +type CBCMessage struct { + Id int64 `json:"id" gorm:"column:id"` // CB消息ID + NeType string `json:"neType" gorm:"column:ne_type"` // 网元类型 + NeId string `json:"neId" gorm:"column:ne_id"` // 网元ID + MessageJson json.RawMessage `json:"messageJson" gorm:"column:message_json"` // 消息内容JSON + Status CBCEventStatus `json:"status" gorm:"column:status"` // 消息状态 + Detail string `json:"detail" gorm:"column:detail"` // 详情 + CreatedAt int64 `json:"createdAt" gorm:"column:created_at"` // 创建时间 + UpdatedAt int64 `json:"updatedAt" gorm:"column:updated_at"` // 更新时间 +} + +// TableName 表名称 +func (*CBCMessage) TableName() string { + return "cbc_message" +} + +// Scan 实现 sql.Scanner 接口,支持从数据库字符串转为 CBCEventStatus +func (s *CBCEventStatus) Scan(value interface{}) error { + switch v := value.(type) { + case string: + *s = ParseCBCEventStatus(v) + return nil + case []byte: + *s = ParseCBCEventStatus(string(v)) + return nil + case int64: + *s = CBCEventStatus(v) + return nil + case int: + *s = CBCEventStatus(v) + return nil + default: + return errors.New("unsupported Scan type for CBCEventStatus") + } +} + +// Value 实现 driver.Valuer 接口,支持将 CBCEventStatus 存为字符串 +func (s CBCEventStatus) Value() (driver.Value, error) { + return s.Enum(), nil +} diff --git a/src/modules/network_data/model/cdr_event.go b/src/modules/network_data/model/cdr_event.go new file mode 100644 index 00000000..8b713d13 --- /dev/null +++ b/src/modules/network_data/model/cdr_event.go @@ -0,0 +1,17 @@ +package model + +// CDREvent CDR会话对象 cdr_event +type CDREvent struct { + ID int64 `json:"id" gorm:"column:id;primaryKey;autoIncrement"` + NeType string `json:"neType" gorm:"column:ne_type"` + NeName string `json:"neName" gorm:"column:ne_name"` + RmUid string `json:"rmUid" gorm:"column:rm_uid"` // 可能没有 + Timestamp int64 `json:"timestamp" gorm:"column:timestamp"` // 接收到的timestamp秒级存储毫秒时间戳 + CdrJson string `json:"cdrJSON" gorm:"column:cdr_json"` // data JSON String + CreatedAt int64 `json:"createdAt" gorm:"column:created_at"` // 记录创建存储毫秒 +} + +// TableName 表名称 +func (*CDREvent) TableName() string { + return "cdr_event" +} diff --git a/src/modules/network_data/model/perf_kpi.go b/src/modules/network_data/model/kpi_report.go similarity index 76% rename from src/modules/network_data/model/perf_kpi.go rename to src/modules/network_data/model/kpi_report.go index e78e678b..626834c2 100644 --- a/src/modules/network_data/model/perf_kpi.go +++ b/src/modules/network_data/model/kpi_report.go @@ -1,18 +1,18 @@ package model -// GoldKPITitle 黄金指标标题信息对象 kpi_title -type GoldKPITitle struct { - ID string `json:"id" gorm:"column:id;primaryKey;autoIncrement"` - NeType string `json:"neType" gorm:"column:ne_type"` - KPIID string `json:"kpiId" gorm:"column:kpi_id"` +// KpiTitle 指标标题信息对象 kpi_title +type KpiTitle struct { + ID int64 `json:"id" gorm:"column:id;primaryKey;autoIncrement"` + NeType string `json:"neType" gorm:"column:ne_type"` // 网元类型 + KpiId string `json:"kpiId" gorm:"column:kpi_id"` // KPI标识 TitleJson string `json:"titleJson" gorm:"column:title_json"` - CnTitle string `json:"cnTitle" gorm:"column:cn_title"` - EnTitle string `json:"enTitle" gorm:"column:en_title"` - StatusFlag string `json:"statusFlag" gorm:"column:status_flag"` + CnTitle string `json:"cnTitle" gorm:"column:cn_title"` // 中文名 + EnTitle string `json:"enTitle" gorm:"column:en_title"` // 英文名 + StatusFlag string `json:"statusFlag" gorm:"column:status_flag"` // 状态标识 1:启用 0:禁用 } // TableName 表名称 -func (*GoldKPITitle) TableName() string { +func (*KpiTitle) TableName() string { return "kpi_title" } @@ -36,8 +36,8 @@ func (*KpiReport) TableName() string { return "kpi_report" } -// GoldKPIQuery 黄金指标查询参数结构体 -type GoldKPIQuery struct { +// KPIQuery 指标查询参数结构体 +type KPIQuery struct { NeType string `form:"neType" binding:"required"` //NeID string `form:"neId" binding:"required"` NeID string `form:"neId"` diff --git a/src/modules/network_data/model/ue_event.go b/src/modules/network_data/model/ue_event.go new file mode 100644 index 00000000..f8116616 --- /dev/null +++ b/src/modules/network_data/model/ue_event.go @@ -0,0 +1,19 @@ +package model + +// UEEvent UE会话对象 ue_event +type UEEvent struct { + ID int64 `json:"id" gorm:"column:id;primaryKey;autoIncrement"` + NeType string `json:"neType" gorm:"column:ne_type"` + NeName string `json:"neName" gorm:"column:ne_name"` + RmUID string `json:"rmUID" gorm:"column:rm_uid"` // 可能没有 + Timestamp int64 `json:"timestamp" gorm:"column:timestamp"` // 接收到时间 + EventType string `json:"eventType" gorm:"column:event_type"` // 事件类型 + EventJSONStr string `json:"eventJSON" gorm:"column:event_json"` // data JSON String + CreatedAt int64 `json:"createdAt" gorm:"column:created_at"` // 记录创建存储毫秒 + TenantID string `json:"tenantID" gorm:"column:tenant_id"` // 租户ID +} + +// TableName 表名称 +func (*UEEvent) TableName() string { + return "ue_event" +} diff --git a/src/modules/network_data/network_data.go b/src/modules/network_data/network_data.go index 7252051d..dbdb1094 100644 --- a/src/modules/network_data/network_data.go +++ b/src/modules/network_data/network_data.go @@ -388,5 +388,5 @@ func Setup(router *gin.Engine) { // InitLoad 初始参数 func InitLoad() { // 启动时,加载UPF上下行流量 - go service.NewPerfKPI.UPFTodayFlowLoad(30) + go service.NewKpiReport.UPFTodayFlowLoad(30) } diff --git a/src/modules/network_data/repository/alarm_event.go b/src/modules/network_data/repository/alarm_event.go new file mode 100644 index 00000000..53c646d6 --- /dev/null +++ b/src/modules/network_data/repository/alarm_event.go @@ -0,0 +1,169 @@ +package repository + +import ( + "time" + + "be.ems/src/framework/database/db" + "be.ems/src/framework/logger" + "be.ems/src/modules/network_data/model" +) + +// 实例化数据层 AlarmEvent 结构体 +var NewAlarmEvent = &AlarmEvent{} + +// AlarmEvent 告警 数据层处理 +type AlarmEvent struct{} + +// SelectByPage 分页查询集合 +func (r AlarmEvent) SelectByPage(query model.AlarmEventQuery) ([]model.AlarmEvent, int64) { + tx := db.DB("").Model(&model.AlarmEvent{}) + // 查询条件拼接 + if query.NeType != "" { + tx = tx.Where("ne_type = ?", query.NeType) + } + if query.NeID != "" { + tx = tx.Where("ne_id = ?", query.NeID) + } + if query.AlarmCode != "" { + tx = tx.Where("alarm_code = ?", query.AlarmCode) + } + if query.AlarmStatus != "" { + tx = tx.Where("alarm_status = ?", query.AlarmStatus) + } + if query.BeginTime != 0 { + tx = tx.Where("event_time >= ?", query.BeginTime) + } + if query.EndTime != 0 { + tx = tx.Where("event_time <= ?", query.EndTime) + } + + // 查询结果 + var total int64 = 0 + rows := []model.AlarmEvent{} + + // 查询数量为0直接返回 + if err := tx.Count(&total).Error; err != nil || total <= 0 { + return rows, total + } + + // 排序 + if query.SortField != "" { + sortField := query.SortField + if query.SortOrder == "desc" { + sortField = sortField + " desc" + } + tx = tx.Order(sortField) + } + + // 查询数据分页 + pageNum, pageSize := db.PageNumSize(query.PageNum, query.PageSize) + tx = tx.Limit(pageSize).Offset(pageSize * pageNum) + err := tx.Find(&rows).Error + if err != nil { + logger.Errorf("query find err => %v", err.Error()) + return rows, total + } + return rows, total +} + +// Select 查询集合 +func (r AlarmEvent) Select(param model.AlarmEvent) []model.AlarmEvent { + tx := db.DB("").Model(&model.AlarmEvent{}) + // 查询条件拼接 + if param.NeType != "" { + tx = tx.Where("ne_type = ?", param.NeType) + } + if param.NeId != "" { + tx = tx.Where("ne_id = ?", param.NeId) + } + if param.AlarmId != "" { + tx = tx.Where("alarm_id = ?", param.AlarmId) + } + if param.AlarmCode != "" { + tx = tx.Where("alarm_code = ?", param.AlarmCode) + } + if param.AlarmStatus != "" { + tx = tx.Where("alarm_status = ?", param.AlarmStatus) + } + // 查询数据 + rows := []model.AlarmEvent{} + if err := tx.Find(&rows).Error; err != nil { + logger.Errorf("query find err => %v", err.Error()) + return rows + } + return rows +} + +// SelectByIds 通过ID查询 +func (r AlarmEvent) SelectByIds(ids []int64) []model.AlarmEvent { + rows := []model.AlarmEvent{} + if len(ids) <= 0 { + return rows + } + tx := db.DB("").Model(&model.AlarmEvent{}) + // 构建查询条件 + tx = tx.Where("id in ?", ids) + // 查询数据 + if err := tx.Find(&rows).Error; err != nil { + logger.Errorf("query find err => %v", err.Error()) + return rows + } + return rows +} + +// Insert 新增信息 返回新增数据ID +func (r AlarmEvent) Insert(param model.AlarmEvent) int64 { + if param.Timestamp.IsZero() { + param.Timestamp = time.Now() + } + // 执行插入 + if err := db.DB("").Create(¶m).Error; err != nil { + logger.Errorf("insert err => %v", err.Error()) + return 0 + } + return param.ID +} + +// Update 修改信息 返回受影响的行数 +func (r AlarmEvent) Update(param model.AlarmEvent) int64 { + if param.ID <= 0 { + return 0 + } + tx := db.DB("").Model(&model.AlarmEvent{}) + // 构建查询条件 + tx = tx.Where("id = ?", param.ID) + tx = tx.Omit("id", "timestamp") + // 执行更新 + if err := tx.Updates(param).Error; err != nil { + logger.Errorf("update err => %v", err.Error()) + return 0 + } + return tx.RowsAffected +} + +// DeleteByIds 批量删除信息 +func (r AlarmEvent) DeleteByIds(ids []int64) int64 { + if len(ids) <= 0 { + return 0 + } + tx := db.DB("").Where("id in ?", ids) + if err := tx.Delete(&model.AlarmEvent{}).Error; err != nil { + logger.Errorf("delete err => %v", err.Error()) + return 0 + } + return tx.RowsAffected +} + +// SelectAlarmEventSeqLast 查询网元告警最后一条序号 +func (r AlarmEvent) SelectAlarmEventSeqLast(neType, neId string) int64 { + tx := db.DB("").Model(&model.AlarmEvent{}) + tx = tx.Where("ne_type=? and ne_id=?", neType, neId) + tx = tx.Select("alarm_seq").Order("alarm_seq DESC") + // 查询数据 + var AlarmEventSeq int64 = 0 + if err := tx.Limit(1).Find(&AlarmEventSeq).Error; err != nil { + logger.Errorf("query find err => %v", err.Error()) + return AlarmEventSeq + } + return AlarmEventSeq +} diff --git a/src/modules/network_data/repository/alarm_forward_log.go b/src/modules/network_data/repository/alarm_forward_log.go new file mode 100644 index 00000000..4665cfa6 --- /dev/null +++ b/src/modules/network_data/repository/alarm_forward_log.go @@ -0,0 +1,104 @@ +package repository + +import ( + "time" + + "be.ems/src/framework/database/db" + "be.ems/src/framework/logger" + "be.ems/src/modules/network_data/model" +) + +// 实例化数据层 AlarmForwardLog 结构体 +var NewAlarmForwardLog = &AlarmForwardLog{} + +// AlarmForwardLog 告警转发记录表 数据层处理 +type AlarmForwardLog struct{} + +// SelectByPage 分页查询集合 +func (r AlarmForwardLog) SelectByPage(query model.AlarmForwardLogQuery) ([]model.AlarmForwardLog, int64) { + tx := db.DB("").Model(&model.AlarmForwardLog{}) + // 查询条件拼接 + if query.NeType != "" { + tx = tx.Where("ne_type = ?", query.NeType) + } + if query.NeID != "" { + tx = tx.Where("ne_id = ?", query.NeID) + } + if query.BeginTime != 0 { + tx = tx.Where("created_at >= ?", query.BeginTime) + } + if query.EndTime != 0 { + tx = tx.Where("created_at <= ?", query.EndTime) + } + + // 查询结果 + var total int64 = 0 + rows := []model.AlarmForwardLog{} + + // 查询数量为0直接返回 + if err := tx.Count(&total).Error; err != nil || total <= 0 { + return rows, total + } + + // 排序 + if query.SortField != "" { + sortField := query.SortField + if query.SortOrder == "desc" { + sortField = sortField + " desc" + } + tx = tx.Order(sortField) + } + + // 查询数据分页 + pageNum, pageSize := db.PageNumSize(query.PageNum, query.PageSize) + tx = tx.Limit(pageSize).Offset(pageSize * pageNum) + err := tx.Find(&rows).Error + if err != nil { + logger.Errorf("query find err => %v", err.Error()) + return rows, total + } + return rows, total +} + +// SelectByIds 通过ID查询 +func (r AlarmForwardLog) SelectByIds(ids []int64) []model.AlarmForwardLog { + rows := []model.AlarmForwardLog{} + if len(ids) <= 0 { + return rows + } + tx := db.DB("").Model(&model.AlarmForwardLog{}) + // 构建查询条件 + tx = tx.Where("id in ?", ids) + // 查询数据 + if err := tx.Find(&rows).Error; err != nil { + logger.Errorf("query find err => %v", err.Error()) + return rows + } + return rows +} + +// DeleteByIds 批量删除信息 +func (r AlarmForwardLog) DeleteByIds(ids []int64) int64 { + if len(ids) <= 0 { + return 0 + } + tx := db.DB("").Where("id in ?", ids) + if err := tx.Delete(&model.AlarmForwardLog{}).Error; err != nil { + logger.Errorf("delete err => %v", err.Error()) + return 0 + } + return tx.RowsAffected +} + +// Insert 新增信息 +func (r AlarmForwardLog) Insert(param model.AlarmForwardLog) int64 { + if param.CreatedAt.IsZero() { + param.CreatedAt = time.Now() + } + // 执行插入 + if err := db.DB("").Create(¶m).Error; err != nil { + logger.Errorf("insert err => %v", err.Error()) + return 0 + } + return param.ID +} diff --git a/src/modules/network_data/repository/alarm_log.go b/src/modules/network_data/repository/alarm_log.go new file mode 100644 index 00000000..8a4f1ba6 --- /dev/null +++ b/src/modules/network_data/repository/alarm_log.go @@ -0,0 +1,107 @@ +package repository + +import ( + "time" + + "be.ems/src/framework/database/db" + "be.ems/src/framework/logger" + "be.ems/src/modules/network_data/model" +) + +// 实例化数据层 AlarmLog 结构体 +var NewAlarmLog = &AlarmLog{} + +// AlarmLog 基站状态记录表 数据层处理 +type AlarmLog struct{} + +// SelectByPage 分页查询集合 +func (r AlarmLog) SelectByPage(query model.AlarmLogQuery) ([]model.AlarmLog, int64) { + tx := db.DB("").Model(&model.AlarmLog{}) + // 查询条件拼接 + if query.NeType != "" { + tx = tx.Where("ne_type = ?", query.NeType) + } + if query.NeID != "" { + tx = tx.Where("ne_id = ?", query.NeID) + } + if query.AlarmStatus != "" { + tx = tx.Where("alarm_status = ?", query.AlarmStatus) + } + if query.BeginTime != 0 { + tx = tx.Where("created_at >= ?", query.BeginTime) + } + if query.EndTime != 0 { + tx = tx.Where("created_at <= ?", query.EndTime) + } + + // 查询结果 + var total int64 = 0 + rows := []model.AlarmLog{} + + // 查询数量为0直接返回 + if err := tx.Count(&total).Error; err != nil || total <= 0 { + return rows, total + } + + // 排序 + if query.SortField != "" { + sortField := query.SortField + if query.SortOrder == "desc" { + sortField = sortField + " desc" + } + tx = tx.Order(sortField) + } + + // 查询数据分页 + pageNum, pageSize := db.PageNumSize(query.PageNum, query.PageSize) + tx = tx.Limit(pageSize).Offset(pageSize * pageNum) + err := tx.Find(&rows).Error + if err != nil { + logger.Errorf("query find err => %v", err.Error()) + return rows, total + } + return rows, total +} + +// SelectByIds 通过ID查询 +func (r AlarmLog) SelectByIds(ids []int64) []model.AlarmLog { + rows := []model.AlarmLog{} + if len(ids) <= 0 { + return rows + } + tx := db.DB("").Model(&model.AlarmLog{}) + // 构建查询条件 + tx = tx.Where("id in ?", ids) + // 查询数据 + if err := tx.Find(&rows).Error; err != nil { + logger.Errorf("query find err => %v", err.Error()) + return rows + } + return rows +} + +// DeleteByIds 批量删除信息 +func (r AlarmLog) DeleteByIds(ids []int64) int64 { + if len(ids) <= 0 { + return 0 + } + tx := db.DB("").Where("id in ?", ids) + if err := tx.Delete(&model.AlarmLog{}).Error; err != nil { + logger.Errorf("delete err => %v", err.Error()) + return 0 + } + return tx.RowsAffected +} + +// Insert 新增信息 +func (r AlarmLog) Insert(param model.AlarmLog) int64 { + if param.CreatedAt.IsZero() { + param.CreatedAt = time.Now() + } + // 执行插入 + if err := db.DB("").Create(¶m).Error; err != nil { + logger.Errorf("insert err => %v", err.Error()) + return 0 + } + return param.ID +} diff --git a/src/modules/network_data/repository/cbc_message.go b/src/modules/network_data/repository/cbc_message.go new file mode 100644 index 00000000..f37c9866 --- /dev/null +++ b/src/modules/network_data/repository/cbc_message.go @@ -0,0 +1,273 @@ +package repository + +import ( + "encoding/json" + "fmt" + "time" + + "be.ems/src/framework/database/db" + "be.ems/src/modules/network_data/model" + "gorm.io/gorm" +) + +// 实例化数据层 UEEvent 结构体 +var NewCBCMessage = &CBCMessage{} + +// UEEvent UE会话事件 数据层处理 +type CBCMessage struct{} + +// SelectCBCMessage 根据条件分页查询CB消息 +func (s *CBCMessage) SelectByPage(query model.CBCMessageQuery) ([]model.CBCMessage, int, error) { + var msg []model.CBCMessage + var total int64 + + tx := db.DB("").Table("cbc_message") + + if query.NeType != "" { + tx = tx.Where("ne_type = ?", query.NeType) + } + if query.NeId != "" { + tx = tx.Where("ne_id = ?", query.NeId) + } + if query.EventName != "" { + tx = tx.Where("JSON_EXTRACT(message_json, '$.eventName') = ?", query.EventName) + } + if query.Status != "" { + tx = tx.Where("status = ?", query.Status) + } + + var startMicro, endMicro int64 + var err error + if query.StartTime != "" { + startMicro, err = parseTimeToMilli(query.StartTime) + if err != nil { + return nil, 0, fmt.Errorf("invalid start time: %w", err) + } + } + if query.EndTime != "" { + endMicro, err = parseTimeToMilli(query.EndTime) + if err != nil { + return nil, 0, fmt.Errorf("invalid end time: %w", err) + } + } + if startMicro > 0 && endMicro > 0 { + tx = tx.Where("created_at BETWEEN ? AND ?", startMicro, endMicro) + } else if startMicro > 0 { + tx = tx.Where("created_at >= ?", startMicro) + } else if endMicro > 0 { + tx = tx.Where("created_at <= ?", endMicro) + } + + // 统计总数 + if err := tx.Count(&total).Error; err != nil { + return nil, 0, fmt.Errorf("failed to count CBC message: %w", err) + } + + // 分页查询 + offset := (query.PageNum - 1) * query.PageSize + if err := tx.Limit(query.PageSize).Offset(offset).Order("created_at desc").Find(&msg).Error; err != nil { + return nil, 0, fmt.Errorf("failed to select CBC message: %w", err) + } + + return msg, int(total), nil +} + +// SelectCBCMessageByPage 分页查询CB消息 +// @Description 分页查询CB消息 +// @param page 页码 +// @param pageSize 每页大小 +// @return []model.CBCMessage CB消息列表 +// @return int 总记录数 +// @return error 错误信息 +// @example +// SelectByPage(1, 10) +// func (s *CBCMessage) SelectByPage(pageNum int, pageSize int) ([]model.CBCMessage, int, error) { +// var tickets []model.CBCMessage +// var total int64 + +// // 统计总数 +// if err := db.DB("").Table("cbc_message").Count(&total).Error; err != nil { +// return nil, 0, fmt.Errorf("failed to count CBC message: %w", err) +// } + +// // 分页查询 +// offset := (pageNum - 1) * pageSize +// if err := db.DB("").Table("cbc_message"). +// Limit(pageSize). +// Offset(offset). +// Find(&tickets).Error; err != nil { +// return nil, 0, fmt.Errorf("failed to select CBC message: %w", err) +// } + +// return tickets, int(total), nil +// } + +// InsertCBCMessage 插入CB消息 +// @Description 插入CB消息 +// @param msg CB消息对象 +// @return error 错误信息 +// @example +// CBCMessage.InsertCBCMessage(msg) +func (s *CBCMessage) Insert(msg model.CBCMessage) error { + msg.CreatedAt = time.Now().UnixMilli() + // 这里可以使用ORM或其他方式将ticket插入到数据库中 + if err := db.DB("").Table("cbc_message").Create(&msg).Error; err != nil { + return fmt.Errorf("failed to insert CBC message: %w", err) + } + return nil +} + +// SelectCBCMessageById 根据工单ID查询CB消息 +// @Description 根据工单ID查询CB消息 +// @param id 工单ID +// @return *model.CBCMessage CB消息对象 +// @return error 错误信息 +// @example +// CBCMessage.SelectCBCMessageById(12345) +func (s *CBCMessage) SelectById(id int64) (*model.CBCMessage, error) { + var msg model.CBCMessage + if err := db.DB("").Table("cbc_message"). + Where("id = ?", id). + First(&msg).Error; err != nil { + if err == gorm.ErrRecordNotFound { + return nil, nil + } + return nil, fmt.Errorf("failed to select CBC message: %w", err) + } + return &msg, nil +} + +// SelectByEventName 根据事件名称查询CB消息 +func (s *CBCMessage) SelectByEventName(eventName string) (*model.CBCMessage, error) { + var msg model.CBCMessage + if err := db.DB("").Table("cbc_message"). + Where("JSON_EXTRACT(message_json, '$.eventName') = ?", eventName). + First(&msg).Error; err != nil { + if err == gorm.ErrRecordNotFound { + return nil, nil + } + return nil, err + } + return &msg, nil +} + +// UpdateCBCMessage 更新CB消息 +// @Description 更新CB消息 +// @param msg CB消息对象 +// @return error 错误信息 +// @example +// mfCBCMessageService.UpdateCBCMessage(msg) +func (s *CBCMessage) Update(id int64, messageJson json.RawMessage) (*model.CBCMessage, error) { + var msg model.CBCMessage + now := time.Now().UnixMilli() + + err := db.DB("").Transaction(func(tx *gorm.DB) error { + // 在事务中更新 + if err := tx.Table("cbc_message"). + Where("id = ?", id). + Updates(map[string]any{ + "message_json": messageJson, + "updated_at": now, + }).Error; err != nil { + return fmt.Errorf("failed to update CBC message: %w", err) + } + + // 在事务中查询更新后的记录 + if err := tx.Table("cbc_message"). + Where("id = ?", id). + First(&msg).Error; err != nil { + return fmt.Errorf("failed to fetch updated CBC message: %w", err) + } + + return nil + }) + + if err != nil { + return nil, err + } + + return &msg, nil +} + +// UpdateCBCMessage 更新CB消息 +// @Description 更新CB消息 +// @param msg CB消息对象 +// @return error 错误信息 +// @example +// UpdateCBCMessageDetail(msg) +func (s *CBCMessage) UpdateDetail(eventName, detail string) error { + now := time.Now().UnixMilli() + if err := db.DB("").Table("cbc_message"). + Where("JSON_EXTRACT(message_json, '$.eventName') = ?", eventName). + Updates(map[string]any{ + "detail": detail, + "updated_at": now, + }).Error; err != nil { + return fmt.Errorf("failed to update CBC message: %w", err) + } + + return nil +} + +// DeleteCBCMessage 删除CB消息 +// @Description 删除CB消息 +// @param id 工单ID +// @return error 错误信息 +// @example +// DeleteCBCMessage(12345) +func (s *CBCMessage) Delete(id int64) error { + // 执行删除操作 + if err := db.DB("").Table("cbc_message"). + Where("id = ?", id). + Delete(&model.CBCMessage{}).Error; err != nil { + return fmt.Errorf("failed to delete CBC message: %w", err) + } + return nil +} + +// UpdateCBCMessageStatus 更新CB消息状态 +// @Description 更新CB消息状态,并根据状态变化发送相应的HTTP请求 +// @param status 新状态 +// @return error 错误信息 +func (s *CBCMessage) UpdateStatus(id int64, status model.CBCEventStatus) error { + // 更新数据库状态 + now := time.Now().UnixMilli() + if err := db.DB("").Table("cbc_message"). + Where("id = ?", id). + Updates(map[string]interface{}{ + "status": status, + "updated_at": now, + }).Error; err != nil { + return fmt.Errorf("failed to update CBC message status: %w", err) + } + + return nil +} + +// Select 查询所有CB消息 +func (s *CBCMessage) Select(msgs *[]model.CBCMessage) error { + if err := db.DB("").Table("cbc_message").Find(&msgs).Error; err != nil { + return fmt.Errorf("failed to query CB messages: %w", err) + } + return nil +} + +// SelectByNeId 根据网元ID查询CB消息 +func (s *CBCMessage) SelectByNeId(neId string, msgs *[]model.CBCMessage) error { + if err := db.DB("").Table("cbc_message").Where("ne_id = ?", neId).Find(&msgs).Error; err != nil { + return fmt.Errorf("failed to query CB messages: %w", err) + } + return nil +} + +// 假设 query.StartTime 和 query.EndTime 是 "2006-01-02 15:04:05" 格式字符串 +func parseTimeToMilli(ts string) (int64, error) { + if ts == "" { + return 0, nil + } + t, err := time.ParseInLocation("2006-01-02 15:04:05", ts, time.Local) + if err != nil { + return 0, err + } + return t.UnixMilli(), nil +} diff --git a/src/modules/network_data/repository/cdr_event.go b/src/modules/network_data/repository/cdr_event.go new file mode 100644 index 00000000..37ef1c33 --- /dev/null +++ b/src/modules/network_data/repository/cdr_event.go @@ -0,0 +1,205 @@ +package repository + +import ( + "fmt" + "sort" + "strings" + "time" + + "be.ems/src/framework/database/db" + "be.ems/src/framework/logger" + "be.ems/src/modules/network_data/model" +) + +// 实例化数据层 CDREvent 结构体 +var NewCDREvent = &CDREvent{} + +// CDREvent CDR会话事件 数据层处理 +type CDREvent struct{} + +// SelectByPage 分页查询集合 +func (r CDREvent) SelectByPage(neType string, query map[string]string) ([]model.CDREvent, int64) { + // 表名 + tableName := fmt.Sprintf("cdr_event_%s", strings.ToLower(neType)) + tx := db.DB("").Table(tableName).Model(&model.CDREvent{}) + // 查询条件拼接 + if v, ok := query["rmUID"]; ok && v != "" { + tx = tx.Where("rm_uid = ?", v) + } + if v, ok := query["beginTime"]; ok && v != "" { + if len(v) == 10 { + v = v + "000" + } + tx = tx.Where("created_at >= ?", v) + } + if v, ok := query["endTime"]; ok && v != "" { + if len(v) == 10 { + v = v + "000" + } + tx = tx.Where("created_at <= ?", v) + } + + // 各个网元特殊查询条件 + switch neType { + case "SMSC": + if v, ok := query["callerParty"]; ok && v != "" { + tx = tx.Where("JSON_EXTRACT(cdr_json, '$.callerParty') like ?", fmt.Sprintf("%%%s%%", v)) + } + if v, ok := query["calledParty"]; ok && v != "" { + tx = tx.Where("JSON_EXTRACT(cdr_json, '$.calledParty') like ?", fmt.Sprintf("%%%s%%", v)) + } + if v, ok := query["recordType"]; ok && v != "" { + recordTypes := strings.Split(v, ",") + var querytrArr []string + for _, recordType := range recordTypes { + querytrArr = append(querytrArr, fmt.Sprintf("JSON_EXTRACT(cdr_json, '$.recordType') = '%s'", recordType)) + } + tx = tx.Where(fmt.Sprintf("( %s )", strings.Join(querytrArr, " OR "))) + } + case "SMF": + if v, ok := query["recordType"]; ok && v != "" { + tx = tx.Where("JSON_EXTRACT(cdr_json, '$.recordType') = ?", v) + } + if v, ok := query["subscriberID"]; ok && v != "" { + tx = tx.Where("JSON_EXTRACT(cdr_json, '$.subscriberIdentifier.subscriptionIDData') like ?", fmt.Sprintf("%%%s%%", v)) + } + if v, ok := query["dnn"]; ok && v != "" { + tx = tx.Where("JSON_EXTRACT(cdr_json, '$.pDUSessionChargingInformation.dNNID') = ?", v) + } + case "SGWC": + if v, ok := query["imsi"]; ok && v != "" { + tx = tx.Where("JSON_EXTRACT(cdr_json, '$.servedIMSI') like ?", fmt.Sprintf("%%%s%%", v)) + } + if v, ok := query["msisdn"]; ok && v != "" { + tx = tx.Where("JSON_EXTRACT(cdr_json, '$.servedMSISDN') like ?", fmt.Sprintf("%%%s%%", v)) + } + case "IMS": + if v, ok := query["callerParty"]; ok && v != "" { + tx = tx.Where("JSON_EXTRACT(cdr_json, '$.callerParty') like ?", fmt.Sprintf("%%%s%%", v)) + } + if v, ok := query["calledParty"]; ok && v != "" { + tx = tx.Where("JSON_EXTRACT(cdr_json, '$.calledParty') like ?", fmt.Sprintf("%%%s%%", v)) + } + } + + var total int64 = 0 + rows := []model.CDREvent{} + + // 查询数量 长度为0直接返回 + if err := tx.Count(&total).Error; err != nil || total <= 0 { + return rows, total + } + + // 分页 + if query["pageNum"] != "" && query["pageSize"] != "" { + pageNum, pageSize := db.PageNumSize(query["pageNum"], query["pageSize"]) + tx = tx.Offset(int(pageNum * pageSize)).Limit(int(pageSize)) + } + + // 查询数据 + if err := tx.Find(&rows).Error; err != nil { + logger.Errorf("query err => %v", err) + } + + // 排序 + if v, ok := query["sortField"]; ok && v != "" { + sortField := v + sortOrder := "asc" + if o, ok := query["sortOrder"]; ok && o != "" { + if o == "desc" { + sortOrder = "desc" + } else { + sortOrder = "asc" + } + } + sort.SliceStable(rows, func(i, j int) bool { + // 支持的排序字段映射 + fieldGetters := map[string]func(*model.CDREvent) any{ + "id": func(row *model.CDREvent) any { return row.ID }, + "timestamp": func(row *model.CDREvent) any { return row.Timestamp }, + // 可添加更多支持的字段 + } + + // 获取字段 getter 函数 + getter, ok := fieldGetters[sortField] + if !ok { + // 非法字段,使用默认排序(id升序) + return rows[i].ID < rows[j].ID + } + + // 获取比较值 + valI, valJ := getter(&rows[i]), getter(&rows[j]) + + // 根据字段类型进行比较 + switch v := valI.(type) { + case int64: + if sortOrder == "desc" { + return v > valJ.(int64) + } + return v < valJ.(int64) + case string: + if sortOrder == "desc" { + return v > valJ.(string) + } + return v < valJ.(string) + default: + // 不支持的字段类型,使用默认排序 + return rows[i].ID < rows[j].ID + } + }) + } + + return rows, total +} + +// SelectByIds 通过ID查询 +func (r CDREvent) SelectByIds(neType string, ids []int64) []model.CDREvent { + rows := []model.CDREvent{} + if len(ids) <= 0 { + return rows + } + // 表名 + tableName := fmt.Sprintf("cdr_event_%s", strings.ToLower(neType)) + tx := db.DB("").Table(tableName).Model(&model.CDREvent{}) + // 构建查询条件 + tx = tx.Where("id in ?", ids) + // 查询数据 + if err := tx.Find(&rows).Error; err != nil { + logger.Errorf("query find err => %v", err.Error()) + return rows + } + return rows +} + +// DeleteByIds 批量删除信息 +func (r CDREvent) DeleteByIds(neType string, ids []int64) int64 { + if len(ids) <= 0 { + return 0 + } + // 表名 + tableName := fmt.Sprintf("cdr_event_%s", strings.ToLower(neType)) + tx := db.DB("").Table(tableName).Where("id in ?", ids) + if err := tx.Delete(&model.CDREvent{}).Error; err != nil { + logger.Errorf("delete err => %v", err.Error()) + return 0 + } + return tx.RowsAffected +} + +// Insert 新增信息 返回新增数据ID +func (r CDREvent) Insert(param model.CDREvent) int64 { + if param.NeType == "" { + return 0 + } + if param.CreatedAt == 0 { + param.CreatedAt = time.Now().UnixMilli() + } + // 表名 + tableName := fmt.Sprintf("cdr_event_%s", strings.ToLower(param.NeType)) + // 执行插入 + if err := db.DB("").Table(tableName).Create(¶m).Error; err != nil { + logger.Errorf("insert err => %v", err.Error()) + return 0 + } + return param.ID +} diff --git a/src/modules/network_data/repository/all_perf_kpi.go b/src/modules/network_data/repository/kpi_report.go similarity index 85% rename from src/modules/network_data/repository/all_perf_kpi.go rename to src/modules/network_data/repository/kpi_report.go index dcfca454..2d9cf03e 100644 --- a/src/modules/network_data/repository/all_perf_kpi.go +++ b/src/modules/network_data/repository/kpi_report.go @@ -4,21 +4,23 @@ import ( "fmt" "sort" "strings" + "time" "be.ems/lib/log" + "be.ems/src/framework/database/db" "be.ems/src/framework/datasource" "be.ems/src/framework/logger" "be.ems/src/modules/network_data/model" ) // 实例化数据层 PerfKPI 结构体 -var NewPerfKPI = &PerfKPI{} +var NewKpiReport = &KpiReport{} -// PerfKPI 性能统计 数据层处理 -type PerfKPI struct{} +// KpiReport 性能统计 数据层处理 +type KpiReport struct{} // SelectGoldKPI 通过网元指标数据信息 -func (r *PerfKPI) SelectGoldKPI(query model.GoldKPIQuery, kpiIds []string) []map[string]any { +func (r *KpiReport) SelectGoldKPI(query model.KPIQuery, kpiIds []string) []map[string]any { // 查询条件拼接 var conditions []string var params []any @@ -93,7 +95,7 @@ func (r *PerfKPI) SelectGoldKPI(query model.GoldKPIQuery, kpiIds []string) []map } // SelectGoldKPI 通过网元指标数据信息 -func (r PerfKPI) SelectKPI(query model.GoldKPIQuery) []model.KpiReport { +func (r *KpiReport) SelectKPI(query model.KPIQuery) []model.KpiReport { rows := []model.KpiReport{} if query.NeType == "" { return rows @@ -164,8 +166,8 @@ func (r PerfKPI) SelectKPI(query model.GoldKPIQuery) []model.KpiReport { } // SelectGoldKPITitle 网元对应的指标名称 -func (r *PerfKPI) SelectGoldKPITitle(neType string) []model.GoldKPITitle { - result := []model.GoldKPITitle{} +func (r *KpiReport) SelectGoldKPITitle(neType string) []model.KpiTitle { + result := []model.KpiTitle{} tx := datasource.DefaultDB().Table("kpi_title").Where("ne_type = ? and status_flag = '1'", neType).Find(&result) if err := tx.Error; err != nil { logger.Errorf("Find err => %v", err) @@ -173,9 +175,27 @@ func (r *PerfKPI) SelectGoldKPITitle(neType string) []model.GoldKPITitle { return result } +// Insert 新增信息 返回新增数据ID +func (r KpiReport) Insert(param model.KpiReport) int64 { + if param.NeType == "" { + return 0 + } + if param.CreatedAt == 0 { + param.CreatedAt = time.Now().UnixMilli() + } + // 表名 + tableName := fmt.Sprintf("kpi_report_%s", strings.ToLower(param.NeType)) + // 执行插入 + if err := db.DB("").Table(tableName).Create(¶m).Error; err != nil { + logger.Errorf("insert err => %v", err.Error()) + return 0 + } + return param.ID +} + // SelectByPageTitle 分页查询集合 -func (r PerfKPI) TitleSelectByPage(query map[string]string) ([]model.GoldKPITitle, int64) { - tx := datasource.DB("").Model(&model.GoldKPITitle{}) +func (r KpiReport) TitleSelectByPage(query map[string]string) ([]model.KpiTitle, int64) { + tx := datasource.DB("").Model(&model.KpiTitle{}) // 查询条件拼接 if v, ok := query["neType"]; ok && v != "" { tx = tx.Where("ne_type = ?", v) @@ -189,7 +209,7 @@ func (r PerfKPI) TitleSelectByPage(query map[string]string) ([]model.GoldKPITitl // 查询结果 var total int64 = 0 - rows := []model.GoldKPITitle{} + rows := []model.KpiTitle{} // 查询数量 长度为0直接返回 if err := tx.Count(&total).Error; err != nil || total <= 0 { @@ -222,23 +242,23 @@ func (r PerfKPI) TitleSelectByPage(query map[string]string) ([]model.GoldKPITitl } // TitleSelect 网元对应的指标名称 -func (r PerfKPI) TitleSelect(param model.GoldKPITitle) []model.GoldKPITitle { - tx := datasource.DB("").Model(&model.GoldKPITitle{}) +func (r KpiReport) TitleSelect(param model.KpiTitle) []model.KpiTitle { + tx := datasource.DB("").Model(&model.KpiTitle{}) // 构建查询条件 - if param.ID != "" { + if param.ID != 0 { tx = tx.Where("id =?", param.ID) } if param.NeType != "" { tx = tx.Where("ne_type =?", param.NeType) } - if param.KPIID != "" { - tx = tx.Where("kpi_id =?", param.KPIID) + if param.KpiId != "" { + tx = tx.Where("kpi_id =?", param.KpiId) } if param.StatusFlag != "" { tx = tx.Where("status_flag = ?", param.StatusFlag) } // 查询数据 - rows := []model.GoldKPITitle{} + rows := []model.KpiTitle{} if err := tx.Find(&rows).Error; err != nil { logger.Errorf("query find err => %v", err.Error()) return rows @@ -247,11 +267,11 @@ func (r PerfKPI) TitleSelect(param model.GoldKPITitle) []model.GoldKPITitle { } // TitleUpdate 修改信息 -func (r PerfKPI) TitleUpdate(param model.GoldKPITitle) int64 { - if param.ID == "" { +func (r KpiReport) TitleUpdate(param model.KpiTitle) int64 { + if param.ID == 0 { return 0 } - tx := datasource.DB("").Model(&model.GoldKPITitle{}) + tx := datasource.DB("").Model(&model.KpiTitle{}) // 构建查询条件 tx = tx.Where("id = ?", param.ID) tx = tx.Omit("id", "kpi_id", "title_json") @@ -264,7 +284,7 @@ func (r PerfKPI) TitleUpdate(param model.GoldKPITitle) int64 { } // SelectUPFTotalFlow 查询UPF总流量 N3上行 N6下行 -func (r *PerfKPI) SelectUPFTotalFlow(neType, rmUID, startDate, endDate string) map[string]any { +func (r *KpiReport) SelectUPFTotalFlow(neType, rmUID, startDate, endDate string) map[string]any { // 查询条件拼接 var conditions []string var params []any @@ -303,7 +323,7 @@ func (r *PerfKPI) SelectUPFTotalFlow(neType, rmUID, startDate, endDate string) m } // SelectIMSBusyHour 查询IMS忙时流量 SCSCF.06呼叫尝试次数 SCSCF.09呼叫成功次数 -func (r *PerfKPI) SelectIMSBusyHour(rmUID string, startDate, endDate int64) []map[string]any { +func (r *KpiReport) SelectIMSBusyHour(rmUID string, startDate, endDate int64) []map[string]any { // 查询条件拼接 var conditions []string var params []any diff --git a/src/modules/network_data/repository/sys_tenant.go b/src/modules/network_data/repository/sys_tenant.go index a314ba19..00e5b063 100644 --- a/src/modules/network_data/repository/sys_tenant.go +++ b/src/modules/network_data/repository/sys_tenant.go @@ -16,7 +16,8 @@ var NewSysTenant = &SysTenant{} type SysTenant struct{} // Query 查询租户信息 ID 名称 -// radioKey 5G_2 4G_3 +// radioKey 5G_2/4G_3 +// imsi 123456789012345 func (r SysTenant) Query(query map[string]string) (int64, string) { // 查询条件拼接 var conditions []string @@ -25,6 +26,10 @@ func (r SysTenant) Query(query map[string]string) (int64, string) { conditions = append(conditions, "st2.tenancy_type = 'RADIO' AND st2.tenancy_key = ?") params = append(params, v) } + if v, ok := query["imsi"]; ok && v != "" { + conditions = append(conditions, "st2.tenancy_type = 'IMSI' AND st2.tenancy_key like ?") + params = append(params, fmt.Sprintf("%%%s%%", v)) + } // 构建查询条件语句 whereSql := "" diff --git a/src/modules/network_data/repository/ue_event.go b/src/modules/network_data/repository/ue_event.go new file mode 100644 index 00000000..ef98e2f6 --- /dev/null +++ b/src/modules/network_data/repository/ue_event.go @@ -0,0 +1,170 @@ +package repository + +import ( + "fmt" + "sort" + "strings" + "time" + + "be.ems/src/framework/database/db" + "be.ems/src/framework/logger" + "be.ems/src/modules/network_data/model" +) + +// 实例化数据层 UEEvent 结构体 +var NewUEEvent = &UEEvent{} + +// UEEvent UE会话事件 数据层处理 +type UEEvent struct{} + +// SelectByPage 分页查询集合 +func (r UEEvent) SelectByPage(neType string, query map[string]string) ([]model.UEEvent, int64) { + // 表名 + tableName := fmt.Sprintf("ue_event_%s", strings.ToLower(neType)) + tx := db.DB("").Table(tableName).Model(&model.UEEvent{}) + // 查询条件拼接 + if v, ok := query["rmUID"]; ok && v != "" { + tx = tx.Where("rm_uid = ?", v) + } + if v, ok := query["beginTime"]; ok && v != "" { + if len(v) == 10 { + v = v + "000" + } + tx = tx.Where("created_at >= ?", v) + } + if v, ok := query["endTime"]; ok && v != "" { + if len(v) == 10 { + v = v + "000" + } + tx = tx.Where("created_at <= ?", v) + } + if v, ok := query["eventType"]; ok && v != "" { + eventTypes := strings.Split(v, ",") + tx = tx.Where("event_type in ?", eventTypes) + } + if v, ok := query["imsi"]; ok && v != "" { + tx = tx.Where("JSON_EXTRACT(event_json, '$.imsi') like ?", fmt.Sprintf("%%%s%%", v)) + } + + // 查询结果 + var total int64 = 0 + rows := []model.UEEvent{} + + // 查询数量 长度为0直接返回 + if err := tx.Count(&total).Error; err != nil || total <= 0 { + return rows, total + } + + // 分页 + if query["pageNum"] != "" && query["pageSize"] != "" { + pageNum, pageSize := db.PageNumSize(query["pageNum"], query["pageSize"]) + tx = tx.Offset(int(pageNum * pageSize)).Limit(int(pageSize)) + } + + // 查询数据 + if err := tx.Find(&rows).Error; err != nil { + logger.Errorf("query err => %v", err) + } + + // 排序 + if v, ok := query["sortField"]; ok && v != "" { + sortField := v + sortOrder := "asc" + if o, ok := query["sortOrder"]; ok && o != "" { + if o == "desc" { + sortOrder = "desc" + } else { + sortOrder = "asc" + } + } + sort.SliceStable(rows, func(i, j int) bool { + // 支持的排序字段映射 + fieldGetters := map[string]func(*model.UEEvent) any{ + "id": func(row *model.UEEvent) any { return row.ID }, + "timestamp": func(row *model.UEEvent) any { return row.CreatedAt }, + "createdAt": func(row *model.UEEvent) any { return row.CreatedAt }, + // 可添加更多支持的字段 + } + + // 获取字段 getter 函数 + getter, ok := fieldGetters[sortField] + if !ok { + // 非法字段,使用默认排序(id升序) + return rows[i].ID < rows[j].ID + } + + // 获取比较值 + valI, valJ := getter(&rows[i]), getter(&rows[j]) + + // 根据字段类型进行比较 + switch v := valI.(type) { + case int64: + if sortOrder == "desc" { + return v > valJ.(int64) + } + return v < valJ.(int64) + case string: + if sortOrder == "desc" { + return v > valJ.(string) + } + return v < valJ.(string) + default: + // 不支持的字段类型,使用默认排序 + return rows[i].ID < rows[j].ID + } + }) + } + return rows, total +} + +// SelectByIds 通过ID查询 +func (r UEEvent) SelectByIds(neType string, ids []int64) []model.UEEvent { + rows := []model.UEEvent{} + if len(ids) <= 0 { + return rows + } + // 表名 + tableName := fmt.Sprintf("ue_event_%s", strings.ToLower(neType)) + tx := db.DB("").Table(tableName).Model(&model.UEEvent{}) + // 构建查询条件 + tx = tx.Where("id in ?", ids) + // 查询数据 + if err := tx.Find(&rows).Error; err != nil { + logger.Errorf("query find err => %v", err.Error()) + return rows + } + return rows +} + +// DeleteByIds 批量删除信息 +func (r UEEvent) DeleteByIds(neType string, ids []int64) int64 { + if len(ids) <= 0 { + return 0 + } + // 表名 + tableName := fmt.Sprintf("ue_event_%s", strings.ToLower(neType)) + tx := db.DB("").Table(tableName).Where("id in ?", ids) + if err := tx.Delete(&model.UEEvent{}).Error; err != nil { + logger.Errorf("delete err => %v", err.Error()) + return 0 + } + return tx.RowsAffected +} + +// Insert 新增信息 返回新增数据ID +func (r UEEvent) Insert(param model.UEEvent) int64 { + if param.NeType == "" { + return 0 + } + if param.CreatedAt == 0 { + param.CreatedAt = time.Now().UnixMilli() + } + // 表名 + tableName := fmt.Sprintf("ue_event_%s", strings.ToLower(param.NeType)) + // 执行插入 + if err := db.DB("").Table(tableName).Create(¶m).Error; err != nil { + logger.Errorf("insert err => %v", err.Error()) + return 0 + } + return param.ID +} diff --git a/src/modules/network_data/service/alarm_event.go b/src/modules/network_data/service/alarm_event.go new file mode 100644 index 00000000..f606303f --- /dev/null +++ b/src/modules/network_data/service/alarm_event.go @@ -0,0 +1,85 @@ +package service + +import ( + "fmt" + "time" + + "github.com/tsmask/go-oam" + + "be.ems/src/modules/network_data/model" + "be.ems/src/modules/network_data/repository" +) + +// 实例化数据层 AlarmEvent 结构体 +var NewAlarmEvent = &AlarmEvent{ + alarmEventRepository: repository.NewAlarmEvent, +} + +// AlarmEvent 告警 服务层处理 +type AlarmEvent struct { + alarmEventRepository *repository.AlarmEvent // 告警数据信息 +} + +// FindByPage 根据条件分页查询 +func (r AlarmEvent) FindByPage(query model.AlarmEventQuery) ([]model.AlarmEvent, int64) { + return r.alarmEventRepository.SelectByPage(query) +} + +// Find 查询 +func (r AlarmEvent) Find(param model.AlarmEvent) []model.AlarmEvent { + return r.alarmEventRepository.Select(param) +} + +// Insert 新增信息 +func (s AlarmEvent) Insert(param model.AlarmEvent) int64 { + return s.alarmEventRepository.Insert(param) +} + +// Update 修改信息 +func (s AlarmEvent) Update(param model.AlarmEvent) int64 { + return s.alarmEventRepository.Update(param) +} + +// DeleteByIds 批量删除信息 +func (r AlarmEvent) DeleteByIds(ids []int64) (int64, error) { + // 检查是否存在 + data := r.alarmEventRepository.SelectByIds(ids) + if len(data) <= 0 { + return 0, fmt.Errorf("no data") + } + + if len(data) == len(ids) { + rows := r.alarmEventRepository.DeleteByIds(ids) + return rows, nil + } + // 删除信息失败! + return 0, fmt.Errorf("delete fail") +} + +// FindAlarmEventSeqLast 查询网元告警最后一条序号 +func (s AlarmEvent) FindAlarmEventSeqLast(neType, neId string) int64 { + return s.alarmEventRepository.SelectAlarmEventSeqLast(neType, neId) +} + +// ClearByIds 批量清除告警信息 +func (r AlarmEvent) ClearByIds(ids []int64, clearUser, clearType string) (int64, error) { + // 检查是否存在 + arr := r.alarmEventRepository.SelectByIds(ids) + if len(arr) <= 0 { + return 0, fmt.Errorf("no data") + } + + if len(arr) == len(ids) { + var rows int64 = 0 + for _, v := range arr { + v.AlarmStatus = oam.ALARM_STATUS_CLEAR + // 告警清除 + v.ClearType = clearType + v.ClearUser = clearUser + v.ClearTime = time.Now() + rows += r.alarmEventRepository.Update(v) + } + return rows, nil + } + return 0, fmt.Errorf("clear fail") +} diff --git a/src/modules/network_data/service/alarm_forward_log.go b/src/modules/network_data/service/alarm_forward_log.go new file mode 100644 index 00000000..34b04073 --- /dev/null +++ b/src/modules/network_data/service/alarm_forward_log.go @@ -0,0 +1,26 @@ +package service + +import ( + "be.ems/src/modules/network_data/model" + "be.ems/src/modules/network_data/repository" +) + +// 实例化数据层 AlarmForwardLog 结构体 +var NewAlarmForwardLog = &AlarmForwardLog{ + alarmForwardLogRepository: repository.NewAlarmForwardLog, +} + +// AlarmForwardLog 告警转发记录表 服务层处理 +type AlarmForwardLog struct { + alarmForwardLogRepository *repository.AlarmForwardLog // 告警转发记录信息 +} + +// FindByPage 根据条件分页查询 +func (r AlarmForwardLog) FindByPage(query model.AlarmForwardLogQuery) ([]model.AlarmForwardLog, int64) { + return r.alarmForwardLogRepository.SelectByPage(query) +} + +// Insert 插入数据 +func (r AlarmForwardLog) Insert(item model.AlarmForwardLog) int64 { + return r.alarmForwardLogRepository.Insert(item) +} diff --git a/src/modules/network_data/service/alarm_log.go b/src/modules/network_data/service/alarm_log.go new file mode 100644 index 00000000..de3cc354 --- /dev/null +++ b/src/modules/network_data/service/alarm_log.go @@ -0,0 +1,26 @@ +package service + +import ( + "be.ems/src/modules/network_data/model" + "be.ems/src/modules/network_data/repository" +) + +// 实例化数据层 AlarmLog 结构体 +var NewAlarmLog = &AlarmLog{ + alarmLogRepository: repository.NewAlarmLog, +} + +// AlarmLog 基站状态记录表 服务层处理 +type AlarmLog struct { + alarmLogRepository *repository.AlarmLog // 基站状态记录信息 +} + +// FindByPage 根据条件分页查询 +func (r AlarmLog) FindByPage(query model.AlarmLogQuery) ([]model.AlarmLog, int64) { + return r.alarmLogRepository.SelectByPage(query) +} + +// Insert 插入数据 +func (r AlarmLog) Insert(item model.AlarmLog) int64 { + return r.alarmLogRepository.Insert(item) +} diff --git a/src/modules/network_data/service/all_alarm.go b/src/modules/network_data/service/all_alarm.go index d28a914c..c331d781 100644 --- a/src/modules/network_data/service/all_alarm.go +++ b/src/modules/network_data/service/all_alarm.go @@ -8,6 +8,7 @@ import ( "be.ems/src/framework/utils/parse" "be.ems/src/modules/network_data/model" "be.ems/src/modules/network_data/repository" + "github.com/tsmask/go-oam" ) // 实例化数据层 Alarm 结构体 @@ -61,6 +62,58 @@ func (s Alarm) FindAlarmSeqLast(neType, neId string) int64 { return s.alarmRepository.SelectAlarmSeqLast(neType, neId) } +// ClearByIds 批量清除告警信息 +func (r Alarm) ClearByIds(ids []string, clearUser, clearType string) (int64, error) { + // 检查是否存在 + arr := r.alarmRepository.SelectByIds(ids) + if len(arr) <= 0 { + return 0, fmt.Errorf("no data") + } + + if len(arr) == len(ids) { + var rows int64 = 0 + for _, v := range arr { + // 状态检查AlarmCode变更告警ID + alarmCode := parse.Number(v.AlarmCode) + if alarmCode == constants.ALARM_STATE_CHECK || alarmCode == constants.ALARM_CMD_CHECK || alarmCode == constants.ALARM_LICENSE_CHECK { + v.AlarmId = fmt.Sprintf("%s%d", v.AlarmCode, v.EventTime.UnixMilli()) + } + v.AlarmStatus = oam.ALARM_STATUS_CLEAR + // 告警清除 + clearTime := time.Now() + v.ClearType = clearType + v.ClearUser = clearUser + v.ClearTime = &clearTime + rows += r.alarmRepository.Update(v) + } + return rows, nil + } + return 0, fmt.Errorf("clear fail") +} + +// AckByIds 批量确认清除告警信息 +func (r Alarm) AckByIds(ids []string, ackUser, ackState string) (int64, error) { + // 检查是否存在 + arr := r.alarmRepository.SelectByIds(ids) + if len(arr) <= 0 { + return 0, fmt.Errorf("no data") + } + + if len(arr) == len(ids) { + var rows int64 = 0 + for _, v := range arr { + // 确认清除 + v.AckState = ackState + ackTime := time.Now() + v.AckTime = &ackTime + v.AckUser = ackUser + rows += r.alarmRepository.Update(v) + } + return rows, nil + } + return 0, fmt.Errorf("ack fail") +} + // AlarmClearByIds 批量清除告警信息 func (r Alarm) AlarmClearByIds(ids []string, clearUser string) (int64, error) { // 检查是否存在 diff --git a/src/modules/network_data/service/cbc_message.go b/src/modules/network_data/service/cbc_message.go new file mode 100644 index 00000000..c5aad39c --- /dev/null +++ b/src/modules/network_data/service/cbc_message.go @@ -0,0 +1,453 @@ +package service + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "strconv" + "time" + + "be.ems/src/framework/i18n" + "be.ems/src/framework/utils/file" + "be.ems/src/modules/network_data/model" + "be.ems/src/modules/network_data/repository" + neService "be.ems/src/modules/network_element/service" + "gorm.io/gorm" +) + +// 实例化数据层 CBCMessage 结构体 +var NewCBCMessage = &CBCMessage{ + cbcMessageRepository: repository.NewCBCMessage, +} + +// CBCMessage CB消息 服务层处理 +type CBCMessage struct { + cbcMessageRepository *repository.CBCMessage // UE会话事件数据信息 +} + +// SelectByPage 根据条件分页查询CB消息 +func (s *CBCMessage) SelectByPage(query model.CBCMessageQuery) ([]model.CBCMessage, int, error) { + return s.cbcMessageRepository.SelectByPage(query) +} + +// SelectCBCMessageById 根据工单ID查询CB消息 +// @Description 根据工单ID查询CB消息 +// @param id 工单ID +// @return *model.CBCMessage CB消息对象 +// @return error 错误信息 +// @example +// CBCMessage.SelectCBCMessageById(12345) +func (s *CBCMessage) SelectById(id int64) (*model.CBCMessage, error) { + return s.cbcMessageRepository.SelectById(id) +} + +func (s *CBCMessage) Insert(msg model.CBCMessage) error { + // 解析messageJson获取eventName + var messageData map[string]interface{} + if err := json.Unmarshal(msg.MessageJson, &messageData); err != nil { + return fmt.Errorf("failed to parse message_json: %w", err) + } + + // 提取eventName + eventName, ok := messageData["eventName"].(string) + if !ok || eventName == "" { + return fmt.Errorf("eventName is required in message_json") + } + + // 检查是否已存在相同的eventName + var err error + var existingMsg *model.CBCMessage + if existingMsg, err = s.cbcMessageRepository.SelectByEventName(eventName); err != nil { + return fmt.Errorf("failed to check existing CBC message: %w", err) + } + + if existingMsg != nil { + return fmt.Errorf("CBC message with eventName '%s' already exists for neType '%s' and neId '%s'", + eventName, existingMsg.NeType, existingMsg.NeId) + } + return s.cbcMessageRepository.Insert(msg) +} + +// UpdateCBCMessage 更新CB消息 +// @Description 更新CB消息 +// @param msg CB消息对象 +// @return error 错误信息 +// @example +// mfCBCMessageService.UpdateCBCMessage(msg) +func (s *CBCMessage) Update(id int64, messageJson json.RawMessage) error { + // 解析messageJson获取eventName + var messageData map[string]interface{} + if err := json.Unmarshal(messageJson, &messageData); err != nil { + return fmt.Errorf("failed to parse message_json: %w", err) + } + // 提取eventName + eventName, ok := messageData["eventName"].(string) + if !ok || eventName == "" { + return fmt.Errorf("eventName is required in message_json") + } + + // 检查是否已存在相同的eventName + var err error + if _, err = s.cbcMessageRepository.SelectByEventName(eventName); err != nil { + if err == gorm.ErrRecordNotFound { + return fmt.Errorf("no existing CBC message found with eventName: %s", eventName) + } + return fmt.Errorf("failed to query existing CBC message: %w", err) + } + + // 如果存在,更新记录 + var msg *model.CBCMessage + if msg, err = s.cbcMessageRepository.Update(id, messageJson); err != nil { + return fmt.Errorf("failed to update CBC message: %w", err) + } + + // 如果状态是ACTIVE,发送更新请求 + if msg.Status == model.CBCEventStatusActive { + if err := s.sendUpdateRequest(*msg); err != nil { + return fmt.Errorf("failed to send update request: %w", err) + } + } + + return nil +} + +// UpdateCBCMessage 更新CB消息 +// @Description 更新CB消息 +// @param msg CB消息对象 +// @return error 错误信息 +// @example +// UpdateCBCMessageDetail(msg) +func (s *CBCMessage) UpdateDetail(eventName, detail string) error { + if err := s.cbcMessageRepository.UpdateDetail(eventName, detail); err != nil { + return fmt.Errorf("failed to update CBC message detail: %w", err) + } + + return nil +} + +// DeleteCBCMessage 删除CB消息 +// @Description 删除CB消息 +// @param id 工单ID +// @return error 错误信息 +// @example +// mfCBCMessageService.DeleteCBCMessage(12345) +func (s *CBCMessage) Delete(id int64) error { + // 先查询记录状态 + var err error + var msg *model.CBCMessage + if msg, err = s.cbcMessageRepository.SelectById(id); err != nil { + if err == gorm.ErrRecordNotFound { + return fmt.Errorf("CBC message with ID %d not found", id) + } + return fmt.Errorf("failed to query CBC message: %w", err) + } + + // 检查状态是否为ACTIVE + if msg.Status == model.CBCEventStatusActive { + return fmt.Errorf("cannot delete an active CBC message (ID: %d)", id) + } + + // 执行删除操作 + if err := s.cbcMessageRepository.Delete(id); err != nil { + return fmt.Errorf("failed to delete CBC message: %w", err) + } + return nil +} + +// UpdateCBCMessageStatus 更新CB消息状态 +// @Description 更新CB消息状态,并根据状态变化发送相应的HTTP请求 +// @param status 新状态 +// @return error 错误信息 +func (s *CBCMessage) UpdateStatus(id int64, status string) error { + newStatus := model.ParseCBCEventStatus(status) + + // 查询所有需要更新的记录 + var err error + var msg *model.CBCMessage + if msg, err = s.cbcMessageRepository.SelectById(id); err != nil { + if err == gorm.ErrRecordNotFound { + return fmt.Errorf("CBC message with ID %d not found", id) + } + return fmt.Errorf("failed to query CBC message: %w", err) + } + + oldStatus := msg.Status + + // 检查状态是否发生变化 + if oldStatus == newStatus { + return fmt.Errorf("CBC message status is already %s", newStatus.Enum()) + } + + // 更新数据库状态 + if err := s.cbcMessageRepository.UpdateStatus(id, newStatus); err != nil { + return fmt.Errorf("failed to update CBC message status: %w", err) + } + + // 根据状态变化发送HTTP请求 + if err := s.handleStatusChange(*msg, oldStatus, newStatus); err != nil { + // 记录错误但不中断处理其他消息 + fmt.Printf("Failed to handle status change for message %d: %v\n", msg.Id, err) + } + + return nil +} + +// UpdateCBCMessageStatus 更新CB消息状态 +// @Description 更新CB消息状态,并根据状态变化发送相应的HTTP请求 +// @param status 新状态 +// @return error 错误信息 +func (s *CBCMessage) UpdateStatusByNeId(neId string, status string) error { + newStatus := model.ParseCBCEventStatus(status) + + // 查询所有需要更新的记录 + msgs := make([]model.CBCMessage, 0) + if err := s.cbcMessageRepository.SelectByNeId(neId, &msgs); err != nil { + return fmt.Errorf("failed to query CB messages: %w", err) + } + + for _, msg := range msgs { + oldStatus := msg.Status + + // 检查状态是否发生变化 + if oldStatus == newStatus { + continue // 状态没有变化,跳过 + } + + // 更新数据库状态 + if err := s.cbcMessageRepository.UpdateStatus(msg.Id, newStatus); err != nil { + return fmt.Errorf("failed to update CBC message status: %w", err) + } + + // 根据状态变化发送HTTP请求 + if err := s.handleStatusChange(msg, oldStatus, newStatus); err != nil { + // 记录错误但不中断处理其他消息 + fmt.Printf("Failed to handle status change for message %d: %v\n", msg.Id, err) + } + } + + return nil +} + +// UpdateCBCMessageStatus 更新CB消息状态 +// @Description 更新CB消息状态,并根据状态变化发送相应的HTTP请求 +// @param status 新状态 +// @return error 错误信息 +func (s *CBCMessage) UpdateAllStatus(status string) error { + newStatus := model.ParseCBCEventStatus(status) + + // 查询所有需要更新的记录 + msgs := make([]model.CBCMessage, 0) + if err := s.cbcMessageRepository.Select(&msgs); err != nil { + return fmt.Errorf("failed to query CB messages: %w", err) + } + + for _, msg := range msgs { + oldStatus := msg.Status + + // 检查状态是否发生变化 + if oldStatus == newStatus { + continue // 状态没有变化,跳过 + } + + // 更新数据库状态 + if err := s.cbcMessageRepository.UpdateStatus(msg.Id, newStatus); err != nil { + return fmt.Errorf("failed to update CBC message status: %w", err) + } + + // 根据状态变化发送HTTP请求 + if err := s.handleStatusChange(msg, oldStatus, newStatus); err != nil { + // 记录错误但不中断处理其他消息 + fmt.Printf("Failed to handle status change for message %d: %v\n", msg.Id, err) + } + } + + return nil +} + +// ExportXlsx 导出数据到 xlsx 文件 +func (r CBCMessage) ExportXlsx(rows []model.CBCMessage, fileName, language string) (string, error) { + // 第一行表头标题 + headerCells := map[string]string{ + "A1": i18n.TKey(language, "alarm.export.alarmType"), + "B1": i18n.TKey(language, "alarm.export.origSeverity"), + "C1": i18n.TKey(language, "alarm.export.alarmTitle"), + "D1": i18n.TKey(language, "alarm.export.eventTime"), + "E1": i18n.TKey(language, "alarm.export.alarmId"), + "F1": i18n.TKey(language, "alarm.export.alarmCode"), + "G1": i18n.TKey(language, "ne.common.neType"), + "H1": i18n.TKey(language, "ne.common.neName"), + "I1": i18n.TKey(language, "ne.common.neId"), + } + + dataCells := make([]map[string]any, 0) + for i, row := range rows { + idx := strconv.Itoa(i + 2) + + cells := map[string]any{ + "A" + idx: row.NeType, + "B" + idx: row.NeId, + "C" + idx: row.MessageJson, // 这里假设 MessageJson 已经是字符串格式 + "D" + idx: row.Status.Enum(), + "E" + idx: row.Detail, + "F" + idx: time.UnixMilli(row.CreatedAt).Format(time.RFC3339), + "G" + idx: time.UnixMilli(row.UpdatedAt).Format(time.RFC3339), + } + + dataCells = append(dataCells, cells) + } + + // 导出数据表格 + return file.WriteSheet(headerCells, dataCells, fileName, "") +} + +// handleStatusChange 处理状态变化时的HTTP请求 +func (s *CBCMessage) handleStatusChange(msg model.CBCMessage, oldStatus, newStatus model.CBCEventStatus) error { + // 从NULL/INACTIVE状态修改为ACTIVE + if (oldStatus == model.CBCEventStatusNull || oldStatus == model.CBCEventStatusInactive) && + newStatus == model.CBCEventStatusActive { + return s.sendActivateRequest(msg) + } + + // 从ACTIVE更改为INACTIVE状态 + if oldStatus == model.CBCEventStatusActive && newStatus == model.CBCEventStatusInactive { + return s.sendDeactivateRequest(msg) + } + + return nil +} + +// getCBCNetworkElement 获取CBC网元的IP和端口信息 +// 这个方法需要根据你的实际网元管理系统来实现 +func (s *CBCMessage) getCBCNetworkElement(neId string) (string, int64, error) { + // 查询网元信息 + neInfo := neService.NewNeInfo.FindByNeTypeAndNeID("CBC", neId) + if neInfo.IP == "" { + return "", 0, fmt.Errorf("CBC network element not found for neId: %s", neId) + } + return neInfo.IP, neInfo.Port, nil +} + +// CBCHTTPClient CBC网元HTTP客户端 +type CBCHTTPClient struct { + client *http.Client + baseURL string +} + +// NewCBCHTTPClient 创建CBC HTTP客户端 +func NewCBCHTTPClient(baseURL string) *CBCHTTPClient { + return &CBCHTTPClient{ + client: &http.Client{ + Timeout: 10 * time.Second, + }, + baseURL: baseURL, + } +} + +// PostMessage 发送POST请求创建消息 +func (c *CBCHTTPClient) PostMessage(messageData []byte) error { + url := fmt.Sprintf("%s/api/v1/cbe/message", c.baseURL) + return c.sendRequest("POST", url, messageData) +} + +// PutMessage 发送PUT请求更新消息 +func (c *CBCHTTPClient) PutMessage(messageData []byte) error { + url := fmt.Sprintf("%s/api/v1/cbe/message", c.baseURL) + return c.sendRequest("PUT", url, messageData) +} + +// DeleteMessage 发送DELETE请求删除消息 +func (c *CBCHTTPClient) DeleteMessage(eventName string, deletePayload []byte) error { + url := fmt.Sprintf("%s/api/v1/cbe/message/%s", c.baseURL, eventName) + return c.sendRequest("DELETE", url, deletePayload) +} + +// sendRequest 发送HTTP请求 +func (c *CBCHTTPClient) sendRequest(method, url string, body []byte) error { + req, err := http.NewRequest(method, url, bytes.NewReader(body)) + if err != nil { + return fmt.Errorf("failed to create %s request: %w", method, err) + } + + req.Header.Set("Content-Type", "application/json") + + resp, err := c.client.Do(req) + if err != nil { + return fmt.Errorf("failed to send %s request: %w", method, err) + } + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + body, _ := io.ReadAll(resp.Body) + return fmt.Errorf("%s request failed with status: %d, body: %s", + method, resp.StatusCode, string(body)) + } + + return nil +} + +// 在CBCMessageService中添加方法 +func (s *CBCMessage) getCBCHTTPClient(neId string) (*CBCHTTPClient, error) { + cbcIP, cbcPort, err := s.getCBCNetworkElement(neId) + if err != nil { + return nil, fmt.Errorf("failed to get CBC network element info: %w", err) + } + + baseURL := fmt.Sprintf("http://%s:%d", cbcIP, cbcPort) + return NewCBCHTTPClient(baseURL), nil +} + +// 重构后的激活请求 +func (s *CBCMessage) sendActivateRequest(msg model.CBCMessage) error { + client, err := s.getCBCHTTPClient(msg.NeId) + if err != nil { + return err + } + // 直接使用 MessageJson 发送POST请求 + return client.PostMessage(msg.MessageJson) +} + +// 重构后的更新请求 +func (s *CBCMessage) sendUpdateRequest(msg model.CBCMessage) error { + client, err := s.getCBCHTTPClient(msg.NeId) + if err != nil { + return err + } + // 直接使用 MessageJson 发送POST请求 + return client.PostMessage(msg.MessageJson) +} + +// 重构后的停用请求 +func (s *CBCMessage) sendDeactivateRequest(msg model.CBCMessage) error { + client, err := s.getCBCHTTPClient(msg.NeId) + if err != nil { + return err + } + + // 解析和构造删除载荷的逻辑保持不变 + var messageData map[string]interface{} + if err := json.Unmarshal(msg.MessageJson, &messageData); err != nil { + return fmt.Errorf("failed to parse message_json: %w", err) + } + + eventName, ok := messageData["eventName"].(string) + if !ok || eventName == "" { + return fmt.Errorf("eventName not found in message_json") + } + + deletePayload := make(map[string]interface{}) + if messageIdProfile, exists := messageData["messageIdProfile"]; exists { + deletePayload["messageIdProfile"] = messageIdProfile + } + if warningAreaList, exists := messageData["warningAreaList"]; exists { + deletePayload["warningAreaList"] = warningAreaList + } + + payloadBytes, err := json.Marshal(deletePayload) + if err != nil { + return fmt.Errorf("failed to marshal delete payload: %w", err) + } + + return client.DeleteMessage(eventName, payloadBytes) +} diff --git a/src/modules/network_data/service/cdr_event.go b/src/modules/network_data/service/cdr_event.go new file mode 100644 index 00000000..76f9fbb0 --- /dev/null +++ b/src/modules/network_data/service/cdr_event.go @@ -0,0 +1,645 @@ +package service + +import ( + "encoding/json" + "fmt" + "strconv" + "strings" + + "be.ems/src/framework/i18n" + "be.ems/src/framework/logger" + "be.ems/src/framework/utils/date" + "be.ems/src/framework/utils/file" + "be.ems/src/framework/utils/parse" + "be.ems/src/modules/network_data/model" + "be.ems/src/modules/network_data/repository" + sysService "be.ems/src/modules/system/service" +) + +// 实例化数据层 CDREvent 结构体 +var NewCDREvent = &CDREvent{ + cdrEventRepository: repository.NewCDREvent, +} + +// CDREvent CDR会话事件 服务层处理 +type CDREvent struct { + cdrEventRepository *repository.CDREvent // CDR会话事件数据信息 +} + +// FindByPage 根据条件分页查询 +func (r CDREvent) FindByPage(neType string, query map[string]string) ([]model.CDREvent, int64) { + return r.cdrEventRepository.SelectByPage(neType, query) +} + +// DeleteByIds 批量删除信息 +func (r CDREvent) DeleteByIds(neType string, ids []int64) (int64, error) { + // 检查是否存在 + rows := r.cdrEventRepository.SelectByIds(neType, ids) + if len(rows) <= 0 { + return 0, fmt.Errorf("not data") + } + + if len(rows) == len(ids) { + rows := r.cdrEventRepository.DeleteByIds(neType, ids) + return rows, nil + } + // 删除信息失败! + return 0, fmt.Errorf("delete fail") +} + +// Insert 新增信息 +func (s CDREvent) Insert(param model.CDREvent) int64 { + return s.cdrEventRepository.Insert(param) +} + +// ExportSMSC 导出数据到 xlsx 文件 +func (r CDREvent) ExportSMSC(rows []model.CDREvent, fileName, language string) (string, error) { + // 第一行表头标题 + headerCells := map[string]string{ + "A1": "ID", + "B1": "NE Name", + "C1": "Record Behavior", + "D1": "Service Type", + "E1": "Caller", + "F1": "Called", + "G1": "Result", + "H1": "Time", + } + // 读取字典数据 CDR 原因码 + dictCDRCauseCode := sysService.NewSysDictData.FindByType("cdr_cause_code") + // 从第二行开始的数据 + dataCells := make([]map[string]any, 0) + for i, row := range rows { + idx := strconv.Itoa(i + 2) + // 解析 JSON 字符串为 map + var cdrJSON map[string]interface{} + err := json.Unmarshal([]byte(row.CdrJson), &cdrJSON) + if err != nil { + logger.Warnf("CDRExport Error parsing JSON: %s", err.Error()) + continue + } + // 记录类型 + recordType := "" + if v, ok := cdrJSON["recordType"]; ok && v != nil { + recordType = v.(string) + } + // 服务类型 + serviceType := "" + if v, ok := cdrJSON["serviceType"]; ok && v != nil { + serviceType = v.(string) + } + // 被叫 + called := "" + if v, ok := cdrJSON["calledParty"]; ok && v != nil { + called = v.(string) + } + // 主叫 + caller := "" + if v, ok := cdrJSON["callerParty"]; ok && v != nil { + caller = v.(string) + } + // 呼叫结果 0失败,1成功 + callResult := "Fail" + if v, ok := cdrJSON["result"]; ok && v != nil { + resultVal := parse.Number(v) + if resultVal == 1 { + callResult = "Success" + } + } + // 结果原因 + if v, ok := cdrJSON["cause"]; ok && v != nil && callResult == "Fail" { + cause := fmt.Sprint(v) + for _, v := range dictCDRCauseCode { + if cause == v.DictValue { + callResult = fmt.Sprintf("%s, %s", callResult, i18n.TKey(language, v.DictLabel)) + break + } + } + } + // 取时间 + timeStr := "" + if v, ok := cdrJSON["updateTime"]; ok && v != nil { + if releaseTime := parse.Number(v); releaseTime > 0 { + timeStr = date.ParseDateToStr(releaseTime, date.YYYY_MM_DDTHH_MM_SSZ) + } else { + timeStr = v.(string) + } + } + + dataCells = append(dataCells, map[string]any{ + "A" + idx: row.ID, + "B" + idx: row.NeName, + "C" + idx: recordType, + "D" + idx: serviceType, + "E" + idx: caller, + "F" + idx: called, + "G" + idx: callResult, + "H" + idx: timeStr, + }) + } + + // 导出数据表格 + return file.WriteSheet(headerCells, dataCells, fileName, "") +} + +// ExportSMF 导出数据到 xlsx 文件 +func (r CDREvent) ExportSMF(rows []model.CDREvent, fileName string) (string, error) { + // 第一行表头标题 + headerCells := map[string]string{ + "A1": "ID", + "B1": "Charging ID", + "C1": "NE Name", + "D1": "Resource Unique ID", + "E1": "Subscriber ID Data", + "F1": "Subscriber ID Type", + "G1": "Data Volume Uplink", + "H1": "Data Volume Downlink", + "I1": "Data Total Volume", + "J1": "Duration", + "K1": "Invocation Time", + "L1": "User Identifier", + "M1": "SSC Mode", + "N1": "DNN ID", + "O1": "PDU Type", + "P1": "RAT Type", + "Q1": "PDU IPv4 Address", + "R1": "Network Function IPv4", + "S1": "PDU IPv6 Address Swith Prefix", + "T1": "Record Network Function ID", + "U1": "Record Type", + "V1": "Record Opening Time", + } + // 从第二行开始的数据 + dataCells := make([]map[string]any, 0) + for i, row := range rows { + idx := strconv.Itoa(i + 2) + // 解析 JSON 字符串为 map + var cdrJSON map[string]interface{} + err := json.Unmarshal([]byte(row.CdrJson), &cdrJSON) + if err != nil { + logger.Warnf("CDRExport Error parsing JSON: %s", err.Error()) + continue + } + // 计费ID + chargingID := "" + if v, ok := cdrJSON["chargingID"]; ok && v != nil { + chargingID = fmt.Sprint(parse.Number(v)) + } + // 订阅 ID 类型 + subscriptionIDType := "-" + // 订阅 ID 数据 + subscriptionIDData := "-" + if v, ok := cdrJSON["subscriberIdentifier"]; ok && v != nil { + if sub, subOk := v.(map[string]any); subOk && sub != nil { + subscriptionIDType = sub["subscriptionIDType"].(string) + subscriptionIDData = sub["subscriptionIDData"].(string) + } + } + + // 网络功能 IPv4 地址 + networkFunctionIPv4Address := "" + if v, ok := cdrJSON["nFunctionConsumerInformation"]; ok && v != nil { + if conInfo, conInfoOk := v.(map[string]any); conInfoOk && conInfo != nil { + networkFunctionIPv4Address = conInfo["networkFunctionIPv4Address"].(string) + } + } + + // 数据量上行链路 + var dataVolumeUplink int64 = 0 + // 数据量下行链路 + var dataVolumeDownlink int64 = 0 + // 数据总量 + var dataTotalVolume int64 = 0 + if v, ok := cdrJSON["listOfMultipleUnitUsage"]; ok && v != nil { + usageList := v.([]any) + if len(usageList) > 0 { + for _, used := range usageList { + usedUnit := used.(map[string]any) + usedUnitList := usedUnit["usedUnitContainer"].([]any) + if len(usedUnitList) > 0 { + for _, data := range usedUnitList { + udata := data.(map[string]any) + if dup, dupOk := udata["dataVolumeUplink"]; dupOk { + dataVolumeUplink += parse.Number(dup) + } + if ddown, ddownOk := udata["dataVolumeDownlink"]; ddownOk { + dataVolumeDownlink += parse.Number(ddown) + } + if dt, dtOk := udata["dataTotalVolume"]; dtOk { + dataTotalVolume += parse.Number(dt) + } + } + } + } + } + } + // 时长 + duration := "-" + if v, ok := cdrJSON["duration"]; ok && v != nil { + duration = fmt.Sprint(parse.Number(v)) + } + // 调用时间 + invocationTimestamp := "" + if v, ok := cdrJSON["invocationTimestamp"]; ok && v != nil { + invocationTimestamp = v.(string) + } + // 记录打开时间 + User_Identifier := "" + SSC_Mode := "" + RAT_Type := "" + DNN_ID := "" + PDU_Type := "" + PDU_IPv4 := "" + PDU_IPv6 := "" + if v, ok := cdrJSON["pDUSessionChargingInformation"]; ok && v != nil { + pduInfo := v.(map[string]any) + + if v, ok := pduInfo["userIdentifier"]; ok && v != nil { + User_Identifier = v.(string) + } + if v, ok := pduInfo["sSCMode"]; ok && v != nil { + SSC_Mode = v.(string) + } + if v, ok := pduInfo["rATType"]; ok && v != nil { + RAT_Type = v.(string) + } + if v, ok := pduInfo["dNNID"]; ok && v != nil { + DNN_ID = v.(string) + } + if v, ok := pduInfo["pDUType"]; ok && v != nil { + PDU_Type = v.(string) + } + if v, ok := pduInfo["pDUAddress"]; ok && v != nil { + pDUAddress := v.(map[string]any) + if addr, ok := pDUAddress["pDUIPv4Address"]; ok && addr != nil { + PDU_IPv4 = addr.(string) + } + if addr, ok := pDUAddress["pDUIPv6AddresswithPrefix"]; ok && addr != nil { + PDU_IPv6 = addr.(string) + } + } + } + + // 记录网络参数ID + recordNFID := "" + if v, ok := cdrJSON["recordingNetworkFunctionID"]; ok && v != nil { + recordNFID = v.(string) + } + + //记录开始时间 + recordOpeningTime := "" + if v, ok := cdrJSON["recordOpeningTime"]; ok && v != nil { + recordOpeningTime = v.(string) + } + + //记录类型 + recordType := "" + if v, ok := cdrJSON["recordType"]; ok && v != nil { + recordType = v.(string) + } + + dataCells = append(dataCells, map[string]any{ + "A" + idx: row.ID, + "B" + idx: chargingID, + "C" + idx: row.NeName, + "D" + idx: row.RmUid, + "E" + idx: subscriptionIDData, + "F" + idx: subscriptionIDType, + "G" + idx: dataVolumeUplink, + "H" + idx: dataVolumeDownlink, + "I" + idx: dataTotalVolume, + "J" + idx: duration, + "K" + idx: invocationTimestamp, + "L" + idx: User_Identifier, + "M" + idx: SSC_Mode, + "N" + idx: DNN_ID, + "O" + idx: PDU_Type, + "P" + idx: RAT_Type, + "Q" + idx: PDU_IPv4, + "R" + idx: networkFunctionIPv4Address, + "S" + idx: PDU_IPv6, + "T" + idx: recordNFID, + "U" + idx: recordType, + "V" + idx: recordOpeningTime, + }) + } + + // 导出数据表格 + return file.WriteSheet(headerCells, dataCells, fileName, "") +} + +// ExportSGWC 导出数据到 xlsx 文件 +func (r CDREvent) ExportSGWC(rows []model.CDREvent, fileName string) (string, error) { + // 第一行表头标题 + headerCells := map[string]string{ + "A1": "ID", + "B1": "NE Name", + "C1": "Resource Unique ID", + "D1": "Charging ID", + "E1": "IMSI", + "F1": "MSISDN", + "G1": "GPRS Uplink", + "H1": "GPRS Downlink", + "I1": "Duration", + "J1": "Invocation Time", + "K1": "PGW Address", + "L1": "SGW Address", + "M1": "RAT Type", + "N1": "PDPPDN Type", + "O1": "PDPPDN Address", + "P1": "Node Address", + "Q1": "Node Type", + "R1": "Record Access Point Name NI", + "S1": "Record Cause For Rec Closing", + "T1": "Record Sequence Number", + "U1": "Local Record Sequence Number", + "V1": "Record Type", + "W1": "Record Opening Time", + } + // 从第二行开始的数据 + dataCells := make([]map[string]any, 0) + for i, row := range rows { + idx := strconv.Itoa(i + 2) + // 解析 JSON 字符串为 map + var cdrJSON map[string]interface{} + err := json.Unmarshal([]byte(row.CdrJson), &cdrJSON) + if err != nil { + logger.Warnf("CDRExport Error parsing JSON: %s", err.Error()) + continue + } + // 计费ID + chargingID := "" + if v, ok := cdrJSON["chargingID"]; ok && v != nil { + chargingID = fmt.Sprint(parse.Number(v)) + } + // IMSI + servedIMSI := "" + if v, ok := cdrJSON["servedIMSI"]; ok && v != nil { + servedIMSI = fmt.Sprint(v) + } + // MSISDN + servedMSISDN := "" + if v, ok := cdrJSON["servedMSISDN"]; ok && v != nil { + servedMSISDN = fmt.Sprint(v) + } + // pGWAddressUsed + pGWAddressUsed := "" + if v, ok := cdrJSON["pGWAddressUsed"]; ok && v != nil { + pGWAddressUsed = fmt.Sprint(v) + headerCells["K1"] = "PGW Address" + } + if v, ok := cdrJSON["GGSNAddress"]; ok && v != nil { + pGWAddressUsed = fmt.Sprint(v) + headerCells["K1"] = "GGSN Address" + } + // sGWAddress + sGWAddress := "" + if v, ok := cdrJSON["sGWAddress"]; ok && v != nil { + sGWAddress = fmt.Sprint(v) + headerCells["L1"] = "SGW Address" + } + if v, ok := cdrJSON["SGSNAddress"]; ok && v != nil { + sGWAddress = fmt.Sprint(v) + headerCells["L1"] = "SGSN Address" + } + // recordType + recordType := "" + if v, ok := cdrJSON["recordType"]; ok && v != nil { + recordType = fmt.Sprint(v) + } + // rATType + rATType := "" + if v, ok := cdrJSON["rATType"]; ok && v != nil { + rATType = fmt.Sprint(v) + } + // pdpPDNType + pdpPDNType := "" + if v, ok := cdrJSON["pdpPDNType"]; ok && v != nil { + pdpPDNType = fmt.Sprint(v) + } + // servedPDPPDNAddress + servedPDPPDNAddress := "" + if v, ok := cdrJSON["servedPDPPDNAddress"]; ok && v != nil { + servedPDPPDNAddress = fmt.Sprint(v) + } + // servedPDPPDNAddress + servingNodeAddress := []string{} + if v, ok := cdrJSON["servingNodeAddress"]; ok && v != nil { + for _, v := range v.([]any) { + servingNodeAddress = append(servingNodeAddress, fmt.Sprint(v)) + } + } + // servingNodeType + servingNodeType := []string{} + if v, ok := cdrJSON["servingNodeType"]; ok && v != nil { + for _, v := range v.([]any) { + if v, ok := v.(map[string]any)["servingNodeType"]; ok && v != nil { + servingNodeType = append(servingNodeType, fmt.Sprint(v)) + } + } + } + // accessPointNameNI + accessPointNameNI := "" + if v, ok := cdrJSON["accessPointNameNI"]; ok && v != nil { + accessPointNameNI = fmt.Sprint(v) + } + // causeForRecClosing + causeForRecClosing := "" + if v, ok := cdrJSON["causeForRecClosing"]; ok && v != nil { + causeForRecClosing = fmt.Sprint(v) + } + // recordSequenceNumber + recordSequenceNumber := "" + if v, ok := cdrJSON["recordSequenceNumber"]; ok && v != nil { + recordSequenceNumber = fmt.Sprint(v) + } + // localRecordSequenceNumber + localRecordSequenceNumber := "" + if v, ok := cdrJSON["localRecordSequenceNumber"]; ok && v != nil { + localRecordSequenceNumber = fmt.Sprint(v) + } + // 数据量上行链路 + var dataVolumeGPRSUplink int64 = 0 + // 数据量下行链路 + var dataVolumeGPRSDownlink int64 = 0 + if v, ok := cdrJSON["listOfTrafficVolumes"]; ok && v != nil { + usageList := v.([]any) + if len(usageList) > 0 { + for _, used := range usageList { + usedUnit := used.(map[string]any) + if dup, dupOk := usedUnit["dataVolumeGPRSUplink"]; dupOk { + dataVolumeGPRSUplink = parse.Number(dup) + } + if ddown, ddownOk := usedUnit["dataVolumeGPRSDownlink"]; ddownOk { + dataVolumeGPRSDownlink = parse.Number(ddown) + } + } + } + } + // 时长 + duration := "-" + if v, ok := cdrJSON["duration"]; ok && v != nil { + duration = fmt.Sprint(parse.Number(v)) + } + // 调用时间 + invocationTimestamp := "" + if v, ok := cdrJSON["recordOpeningTime"]; ok && v != nil { + invocationTimestamp = v.(string) + } + + dataCells = append(dataCells, map[string]any{ + "A" + idx: row.ID, + "B" + idx: row.NeName, + "C" + idx: row.RmUid, + "D" + idx: chargingID, + "E" + idx: servedIMSI, + "F" + idx: servedMSISDN, + "G" + idx: dataVolumeGPRSUplink, + "H" + idx: dataVolumeGPRSDownlink, + "I" + idx: duration, + "J" + idx: invocationTimestamp, + "K" + idx: pGWAddressUsed, + "L" + idx: sGWAddress, + "M" + idx: rATType, + "N" + idx: pdpPDNType, + "O" + idx: servedPDPPDNAddress, + "P" + idx: strings.Join(servingNodeAddress, ","), + "Q" + idx: strings.Join(servingNodeType, ","), + "R" + idx: accessPointNameNI, + "S" + idx: causeForRecClosing, + "T" + idx: recordSequenceNumber, + "U" + idx: localRecordSequenceNumber, + "V" + idx: recordType, + "W" + idx: invocationTimestamp, + }) + } + + // 导出数据表格 + return file.WriteSheet(headerCells, dataCells, fileName, "") +} + +// ExportIMS 导出数据到 xlsx 文件 +func (r CDREvent) ExportIMS(rows []model.CDREvent, fileName, language string) (string, error) { + // 第一行表头标题 + headerCells := map[string]string{ + "A1": "ID", + "B1": "NE Name", + "C1": "Record Behavior", + "D1": "Type", + "E1": "Caller", + "F1": "Called", + "G1": "Duration", + "H1": "Result Code", + "I1": "Result Cause", + "J1": "Call Start Time", + "K1": "Hangup Time", + } + // 读取字典数据 CDR SIP响应代码类别类型 + dictCDRSipCode := sysService.NewSysDictData.FindByType("cdr_sip_code") + // 读取字典数据 CDR SIP响应代码类别类型原因 + dictCDRSipCodeCause := sysService.NewSysDictData.FindByType("cdr_sip_code_cause") + // 读取字典数据 CDR 呼叫类型 + dictCDRCallType := sysService.NewSysDictData.FindByType("cdr_call_type") + // 从第二行开始的数据 + dataCells := make([]map[string]any, 0) + for i, row := range rows { + idx := strconv.Itoa(i + 2) + // 解析 JSON 字符串为 map + var cdrJSON map[string]any + err := json.Unmarshal([]byte(row.CdrJson), &cdrJSON) + if err != nil { + logger.Warnf("CDRExport Error parsing JSON: %s", err.Error()) + continue + } + // 记录类型 + recordType := "" + if v, ok := cdrJSON["recordType"]; ok && v != nil { + recordType = v.(string) + } + // 呼叫类型 + callType := "sms" + callTypeLable := "SMS" + if v, ok := cdrJSON["callType"]; ok && v != nil { + callType = v.(string) + for _, v := range dictCDRCallType { + if callType == v.DictValue { + callTypeLable = i18n.TKey(language, v.DictLabel) + break + } + } + } + // 被叫 + called := "" + if v, ok := cdrJSON["calledParty"]; ok && v != nil { + called = v.(string) + } + // 主叫 + caller := "" + if v, ok := cdrJSON["callerParty"]; ok && v != nil { + caller = v.(string) + } + // 时长 + duration := "-" + if v, ok := cdrJSON["callDuration"]; ok && v != nil && callType != "sms" { + duration = fmt.Sprintf("%ds", parse.Number(v)) + } + // 呼叫结果 非短信都有code作为结果 sms短信都ok + callResult := "Other" + callCause := "Call failure for other reason" + if callType == "sms" { + callResult = "Success" + callCause = "Normal Send" + } else { + if v, ok := cdrJSON["cause"]; ok && v != nil { + cause := fmt.Sprint(v) + for _, v := range dictCDRSipCode { + if cause == v.DictValue { + callResult = i18n.TKey(language, v.DictLabel) + break + } + } + for _, v := range dictCDRSipCodeCause { + if cause == v.DictValue { + callCause = i18n.TKey(language, v.DictLabel) + break + } + } + } + } + // 呼叫时间 + seizureTimeStr := "" + if v, ok := cdrJSON["seizureTime"]; ok && v != nil { + if seizureTime := parse.Number(v); seizureTime > 0 { + seizureTimeStr = date.ParseDateToStr(seizureTime, date.YYYY_MM_DDTHH_MM_SSZ) + } else { + seizureTimeStr = v.(string) + } + } + // 挂断时间 + releaseTimeStr := "" + if v, ok := cdrJSON["releaseTime"]; ok && v != nil { + if releaseTime := parse.Number(v); releaseTime > 0 { + releaseTimeStr = date.ParseDateToStr(releaseTime, date.YYYY_MM_DDTHH_MM_SSZ) + } else { + releaseTimeStr = v.(string) + } + } + + dataCells = append(dataCells, map[string]any{ + "A" + idx: row.ID, + "B" + idx: row.NeName, + "C" + idx: recordType, + "D" + idx: callTypeLable, + "E" + idx: caller, + "F" + idx: called, + "G" + idx: duration, + "H" + idx: callResult, + "I" + idx: callCause, + "J" + idx: seizureTimeStr, + "K" + idx: releaseTimeStr, + }) + } + + // 导出数据表格 + return file.WriteSheet(headerCells, dataCells, fileName, "") +} diff --git a/src/modules/network_data/service/all_perf_kpi.go b/src/modules/network_data/service/kpi_report.go similarity index 81% rename from src/modules/network_data/service/all_perf_kpi.go rename to src/modules/network_data/service/kpi_report.go index fc093f02..d94c2583 100644 --- a/src/modules/network_data/service/all_perf_kpi.go +++ b/src/modules/network_data/service/kpi_report.go @@ -14,26 +14,26 @@ import ( neModel "be.ems/src/modules/network_element/model" ) -// 实例化数据层 PerfKPI 结构体 -var NewPerfKPI = &PerfKPI{ - perfKPIRepository: repository.NewPerfKPI, +// 实例化数据层 KpiReport 结构体 +var NewKpiReport = &KpiReport{ + kpiReportRepository: repository.NewKpiReport, } -// PerfKPI 性能统计 服务层处理 -type PerfKPI struct { - perfKPIRepository *repository.PerfKPI // 性能统计数据信息 +// KpiReport 性能统计 服务层处理 +type KpiReport struct { + kpiReportRepository *repository.KpiReport // 性能统计数据信息 } // SelectGoldKPI 通过网元指标数据信息 -func (r *PerfKPI) SelectGoldKPI(query model.GoldKPIQuery) []map[string]any { +func (r *KpiReport) SelectGoldKPI(query model.KPIQuery) []map[string]any { // 获取数据指标id var kpiIds []string - kpiTitles := r.perfKPIRepository.SelectGoldKPITitle(query.NeType) - for _, kpiId := range kpiTitles { - kpiIds = append(kpiIds, kpiId.KPIID) + kpiTitles := r.kpiReportRepository.SelectGoldKPITitle(query.NeType) + for _, v := range kpiTitles { + kpiIds = append(kpiIds, v.KpiId) } - data := r.perfKPIRepository.SelectGoldKPI(query, kpiIds) + data := r.kpiReportRepository.SelectGoldKPI(query, kpiIds) if data == nil { return []map[string]any{} } @@ -41,29 +41,39 @@ func (r *PerfKPI) SelectGoldKPI(query model.GoldKPIQuery) []map[string]any { } // SelectGoldKPITitle 网元对应的指标名称 -func (r *PerfKPI) SelectGoldKPITitle(neType string) []model.GoldKPITitle { - return r.perfKPIRepository.SelectGoldKPITitle(neType) +func (r *KpiReport) SelectGoldKPITitle(neType string) []model.KpiTitle { + return r.kpiReportRepository.SelectGoldKPITitle(neType) +} + +// FindTitle 网元对应的指标名称 +func (r KpiReport) FindTitle(neType string) []model.KpiTitle { + return r.kpiReportRepository.SelectGoldKPITitle(neType) +} + +// Insert 新增信息 +func (s KpiReport) Insert(param model.KpiReport) int64 { + return s.kpiReportRepository.Insert(param) } // TitleSelectByPage 分页查询指标名称 -func (r *PerfKPI) TitleSelectByPage(query map[string]string) ([]model.GoldKPITitle, int64) { - return r.perfKPIRepository.TitleSelectByPage(query) +func (r *KpiReport) TitleSelectByPage(query map[string]string) ([]model.KpiTitle, int64) { + return r.kpiReportRepository.TitleSelectByPage(query) } // TitleFind 查询信息 -func (r PerfKPI) TitleFind(param model.GoldKPITitle) []model.GoldKPITitle { - return r.perfKPIRepository.TitleSelect(param) +func (r KpiReport) TitleFind(param model.KpiTitle) []model.KpiTitle { + return r.kpiReportRepository.TitleSelect(param) } // TitleUpdate 修改指标状态 -func (r *PerfKPI) TitleUpdate(param model.GoldKPITitle) int64 { - return r.perfKPIRepository.TitleUpdate(param) +func (r *KpiReport) TitleUpdate(param model.KpiTitle) int64 { + return r.kpiReportRepository.TitleUpdate(param) } // FindData 通过网元指标数据信息 -func (s PerfKPI) FindData(query model.GoldKPIQuery) []map[string]any { +func (s KpiReport) FindData(query model.KPIQuery) []map[string]any { // 原始数据 - rows := s.perfKPIRepository.SelectKPI(query) + rows := s.kpiReportRepository.SelectKPI(query) if len(rows) <= 0 { return []map[string]any{} } @@ -169,7 +179,7 @@ func (s PerfKPI) FindData(query model.GoldKPIQuery) []map[string]any { // UPFTodayFlowFind 查询UPF总流量 N3上行 N6下行 // day 统计天数 -func (r PerfKPI) UPFTodayFlowFind(rmUID string, day int) (int64, int64) { +func (r KpiReport) UPFTodayFlowFind(rmUID string, day int) (int64, int64) { // 获取当前日期 now := time.Now() var upTotal, downTotal int64 @@ -197,7 +207,7 @@ func (r PerfKPI) UPFTodayFlowFind(rmUID string, day int) (int64, int64) { } // UPFTodayFlow UPF流量今日统计 -func (r PerfKPI) UPFTodayFlowUpdate(rmUID string, upValue, downValue int64) error { +func (r KpiReport) UPFTodayFlowUpdate(rmUID string, upValue, downValue int64) error { // 按日期存储统计数据 dateKey := time.Now().Format("2006-01-02") key := fmt.Sprintf("%sUPF_FLOW:%s:%s", cachekey.NE_DATA_KEY, rmUID, dateKey) @@ -214,7 +224,7 @@ func (r PerfKPI) UPFTodayFlowUpdate(rmUID string, upValue, downValue int64) erro // UPFTodayFlowLoad UPF上下行数据到redis // day 统计天数 -func (r PerfKPI) UPFTodayFlowLoad(day int) { +func (r KpiReport) UPFTodayFlowLoad(day int) { cacheKeys, _ := redis.GetKeys("", cachekey.NE_KEY+"UPF:*") if len(cacheKeys) == 0 { return @@ -237,7 +247,7 @@ func (r PerfKPI) UPFTodayFlowLoad(day int) { endTime := beginTime + 24*60*60*1000 - 1 // 查询历史数据 // down * 8 / 1000 / 1000 单位M - info := r.perfKPIRepository.SelectUPFTotalFlow("UPF", v.RmUID, fmt.Sprint(beginTime), fmt.Sprint(endTime)) + info := r.kpiReportRepository.SelectUPFTotalFlow("UPF", v.RmUID, fmt.Sprint(beginTime), fmt.Sprint(endTime)) if v, ok := info["up"]; ok && v == nil { info["up"] = 0 } @@ -267,7 +277,7 @@ func (r PerfKPI) UPFTodayFlowLoad(day int) { } // IMSBusyHour IMS忙时流量统计 -func (r PerfKPI) IMSBusyHour(rmUID string, timestamp int64) []map[string]any { +func (r KpiReport) IMSBusyHour(rmUID string, timestamp int64) []map[string]any { t := time.UnixMilli(timestamp) beginTime := t endTime := t @@ -284,7 +294,7 @@ func (r PerfKPI) IMSBusyHour(rmUID string, timestamp int64) []map[string]any { endTime = beginTime.Add(time.Hour - time.Millisecond) } // 转换为毫秒级时间戳 - return r.perfKPIRepository.SelectIMSBusyHour(rmUID, beginTime.UnixMilli(), endTime.UnixMilli()) + return r.kpiReportRepository.SelectIMSBusyHour(rmUID, beginTime.UnixMilli(), endTime.UnixMilli()) } // 定义结构体用于存储话务量值和对应的时间 @@ -294,12 +304,12 @@ type TrafficData struct { } // IMSBusyWeek IMS忙时流量统计 周 -func (r PerfKPI) IMSBusyWeek(rmUID string, weekStart, weekEnd int64) map[string]any { +func (r KpiReport) IMSBusyWeek(rmUID string, weekStart, weekEnd int64) map[string]any { weekStartTime := time.UnixMilli(weekStart) weekEndTime := time.UnixMilli(weekEnd) // 1. 获取一周内每小时的呼叫数据 - data := r.perfKPIRepository.SelectIMSBusyHour(rmUID, weekStartTime.UnixMilli(), weekEndTime.UnixMilli()) + data := r.kpiReportRepository.SelectIMSBusyHour(rmUID, weekStartTime.UnixMilli(), weekEndTime.UnixMilli()) if len(data) == 0 { return map[string]any{ diff --git a/src/modules/network_data/service/ue_event.go b/src/modules/network_data/service/ue_event.go new file mode 100644 index 00000000..4f73bec9 --- /dev/null +++ b/src/modules/network_data/service/ue_event.go @@ -0,0 +1,229 @@ +package service + +import ( + "encoding/json" + "fmt" + "strconv" + + "be.ems/src/framework/i18n" + "be.ems/src/framework/logger" + "be.ems/src/framework/utils/date" + "be.ems/src/framework/utils/file" + "be.ems/src/framework/utils/parse" + "be.ems/src/modules/network_data/model" + "be.ems/src/modules/network_data/repository" + sysService "be.ems/src/modules/system/service" + "github.com/tsmask/go-oam" +) + +// 实例化数据层 UEEvent 结构体 +var NewUEEvent = &UEEvent{ + ueEventRepository: repository.NewUEEvent, +} + +// UEEvent UE会话事件 服务层处理 +type UEEvent struct { + ueEventRepository *repository.UEEvent // UE会话事件数据信息 +} + +// FindByPage 根据条件分页查询 +func (r UEEvent) FindByPage(neType string, query map[string]string) ([]model.UEEvent, int64) { + return r.ueEventRepository.SelectByPage(neType, query) +} + +// DeleteByIds 批量删除信息 +func (r UEEvent) DeleteByIds(neType string, ids []int64) (int64, error) { + // 检查是否存在 + rows := r.ueEventRepository.SelectByIds(neType, ids) + if len(rows) <= 0 { + return 0, fmt.Errorf("no data") + } + + if len(rows) == len(ids) { + rows := r.ueEventRepository.DeleteByIds(neType, ids) + return rows, nil + } + // 删除信息失败! + return 0, fmt.Errorf("delete fail") +} + +// Insert 新增信息 +func (r UEEvent) Insert(param model.UEEvent) int64 { + return r.ueEventRepository.Insert(param) +} + +// ExportAMF 导出数据到 xlsx 文件 +func (r UEEvent) ExportAMF(rows []model.UEEvent, fileName, language string) (string, error) { + // 第一行表头标题 + headerCells := map[string]string{ + "A1": "ID", + "B1": "IMSI", + "C1": "Event Type", + "D1": "Result", + "E1": "Time", + } + // 读取字典数据 UE 事件类型 + dictUEEventType := sysService.NewSysDictData.SelectDictDataByType("ue_event_type") + // 读取字典数据 UE 事件认证代码类型 + dictUEAauthCode := sysService.NewSysDictData.FindByType("ue_auth_code") + // 读取字典数据 UE 事件CM状态 + dictUEEventCmState := sysService.NewSysDictData.FindByType("ue_event_cm_state") + // 从第二行开始的数据 + dataCells := make([]map[string]any, 0) + for i, row := range rows { + idx := strconv.Itoa(i + 2) + // 解析 JSON 字符串为 map + var eventJSON map[string]interface{} + err := json.Unmarshal([]byte(row.EventJSONStr), &eventJSON) + if err != nil { + logger.Warnf("UEExport Error parsing JSON: %s", err.Error()) + continue + } + + // 取IMSI + imsi := "" + if v, ok := eventJSON["imsi"]; ok && v != nil { + imsi = v.(string) + } + // 取类型 + eventType := "" + for _, v := range dictUEEventType { + if row.EventType == v.DictValue { + eventType = i18n.TKey(language, v.DictLabel) + break + } + } + // 取结果 + eventResult := "" + // 取时间 + timeStr := "" + if row.EventType == oam.UENB_TYPE_AUTH { + if v, ok := eventJSON["authTime"]; ok && v != nil { + timeStr = v.(string) + } + if v, ok := eventJSON["authCode"]; ok && v != nil { + eventResult = v.(string) + for _, v := range dictUEAauthCode { + if eventResult == v.DictValue { + eventResult = i18n.TKey(language, v.DictLabel) + break + } + } + } + } + if row.EventType == oam.UENB_TYPE_DETACH { + if v, ok := eventJSON["detachTime"]; ok && v != nil { + timeStr = v.(string) + } + eventResult = "Success" + } + if row.EventType == oam.UENB_TYPE_CM { + if v, ok := eventJSON["changeTime"]; ok && v != nil { + timeStr = v.(string) + } + if v, ok := eventJSON["status"]; ok && v != nil { + eventResult = fmt.Sprint(v) + for _, v := range dictUEEventCmState { + if eventResult == v.DictValue { + eventResult = i18n.TKey(language, v.DictLabel) + break + } + } + } + } + + dataCells = append(dataCells, map[string]any{ + "A" + idx: row.ID, + "B" + idx: imsi, + "C" + idx: eventType, + "D" + idx: eventResult, + "E" + idx: timeStr, + }) + } + + // 导出数据表格 + return file.WriteSheet(headerCells, dataCells, fileName, "") +} + +// ExportMME 导出数据到 xlsx 文件 +func (r UEEvent) ExportMME(rows []model.UEEvent, fileName, language string) (string, error) { + // 第一行表头标题 + headerCells := map[string]string{ + "A1": "ID", + "B1": "IMSI", + "C1": "Event Type", + "D1": "Result", + "E1": "Time", + } + // 读取字典数据 UE 事件类型 + dictUEEventType := sysService.NewSysDictData.FindByType("ue_event_type") + // 读取字典数据 UE 事件认证代码类型 + dictUEAauthCode := sysService.NewSysDictData.FindByType("ue_auth_code") + // 读取字典数据 UE 事件CM状态 + dictUEEventCmState := sysService.NewSysDictData.FindByType("ue_event_cm_state") + // 从第二行开始的数据 + dataCells := make([]map[string]any, 0) + for i, row := range rows { + idx := strconv.Itoa(i + 2) + // 解析 JSON 字符串为 map + var eventJSON map[string]interface{} + err := json.Unmarshal([]byte(row.EventJSONStr), &eventJSON) + if err != nil { + logger.Warnf("UEExport Error parsing JSON: %s", err.Error()) + continue + } + + // 取IMSI + imsi := "" + if v, ok := eventJSON["imsi"]; ok && v != nil { + imsi = v.(string) + } + // 取类型 + eventType := row.EventType + for _, v := range dictUEEventType { + if row.EventType == v.DictValue { + eventType = i18n.TKey(language, v.DictLabel) + break + } + } + // 取结果 + eventResult := "" + if v, ok := eventJSON["result"]; ok && v != nil { + eventResult = v.(string) + if row.EventType == oam.UENB_TYPE_AUTH { + for _, v := range dictUEAauthCode { + if eventResult == v.DictValue { + eventResult = i18n.TKey(language, v.DictLabel) + break + } + } + } + if row.EventType == oam.UENB_TYPE_CM { + for _, v := range dictUEEventCmState { + if eventResult == v.DictValue { + eventResult = i18n.TKey(language, v.DictLabel) + break + } + } + } + } + // 取时间 + timeStr := "" + if v, ok := eventJSON["timestamp"]; ok && v != nil { + rowTime := parse.Number(v) + timeStr = date.ParseDateToStr(rowTime, date.YYYY_MM_DDTHH_MM_SSZ) + } + + dataCells = append(dataCells, map[string]any{ + "A" + idx: row.ID, + "B" + idx: imsi, + "C" + idx: eventType, + "D" + idx: eventResult, + "E" + idx: timeStr, + }) + } + + // 导出数据表格 + return file.WriteSheet(headerCells, dataCells, fileName, "") + +} diff --git a/src/modules/notification/notification.go b/src/modules/notification/notification.go new file mode 100644 index 00000000..cda49a9c --- /dev/null +++ b/src/modules/notification/notification.go @@ -0,0 +1,11 @@ +package notification + +import ( + "be.ems/src/framework/logger" + "github.com/gin-gonic/gin" +) + +// 模块路由注册 +func Setup(router *gin.Engine) { + logger.Infof("开始加载 ====> notification 模块路由") +} diff --git a/src/modules/notification/service/email.go b/src/modules/notification/service/email.go new file mode 100644 index 00000000..183ce5bd --- /dev/null +++ b/src/modules/notification/service/email.go @@ -0,0 +1,126 @@ +package service + +import ( + "bytes" + "crypto/tls" + "fmt" + ht "html/template" + "strings" + "time" + + "github.com/wneessen/go-mail" + + "be.ems/src/framework/config" + "be.ems/src/framework/logger" + "be.ems/src/framework/utils/date" + "be.ems/src/framework/utils/parse" + neDataModel "be.ems/src/modules/network_data/model" +) + +// EmailAlarm 发告警邮件 +func EmailAlarm(a neDataModel.Alarm, neIP string) error { + emailList := fmt.Sprint(config.Get("alarm.alarmEmailForward.emailList")) + if len(emailList) == 0 { + return fmt.Errorf("email list is empty") + } + + // 模板 + htmlBodyTemplate := ` + Alarm information +

Sequence: {{.AlarmSeq}}

+

NE Name: {{.NeName}}

+

NE IP: {{.NeIp}}

+

Title: {{.AlarmTitle}}

+

Severity: {{.OrigSeverity}}

+

Event Time: {{.AlarmTime}}

+

Alarm Status: {{.AlarmStatus}}

+ Automatic sent by OMC, please do not reply! + ` + htmlTpl, err := ht.New("htmltpl").Parse(htmlBodyTemplate) + if err != nil { + return fmt.Errorf("EmailAlarm Parse alarmId:%s fail %s", a.AlarmId, err.Error()) + } + // 参数值 + data := map[string]any{ + "AlarmSeq": a.AlarmSeq, + "NeName": a.NeName, + "NeIp": neIP, + "AlarmTitle": a.AlarmTitle, + "OrigSeverity": a.OrigSeverity, + "AlarmTime": date.ParseDateToStr(a.EventTime, time.RFC3339), + "AlarmStatus": a.AlarmStatus, + } + buffer := bytes.NewBuffer(nil) + if err := htmlTpl.Execute(buffer, data); err != nil { + return fmt.Errorf("EmailAlarm Execute alarmId:%s fail %s", a.AlarmId, err.Error()) + } + htmlStr := buffer.String() + + // 发送邮件 + err = EmailSendHTML(htmlStr, strings.Split(emailList, ",")) + if err != nil { + return fmt.Errorf("EmailAlarm alarmId:%s fail %s", a.AlarmId, err.Error()) + } + return err +} + +// EmailSendHTML 发送HTML邮件 +func EmailSendHTML(htmlStr string, toEmailArr []string) error { + // QQ 邮箱: + // SMTP 服务器地址:smtp.qq.com(SSL协议端口:465/994 | 非SSL协议端口:25) + // 163 邮箱: + // SMTP 服务器地址:smtp.163.com(端口:25) + // host := "mail.agrandtech.com" + // port := 25 + // userName := "smtpext@agrandtech.com" + // password := "1000smtp@omc!" + + emailConf := config.Get("alarm.alarmEmailForward").(map[string]any) + enable := parse.Boolean(emailConf["enable"]) + if !enable { + return fmt.Errorf("email notification not enable") + } + title := fmt.Sprint(emailConf["title"]) + smtp := fmt.Sprint(emailConf["smtp"]) + port := parse.Number(emailConf["port"]) + user := fmt.Sprint(emailConf["user"]) + password := fmt.Sprint(emailConf["password"]) + + message := mail.NewMsg() + // 发件人 + if err := message.From(user); err != nil { + return fmt.Errorf("failed to set From address: %s", err) + } + // 收件人 + hasTo := false + for _, v := range toEmailArr { + if err := message.AddTo(v); err != nil { + logger.Errorf("failed to set To address: %v %s", v, err) + continue + } + hasTo = true + } + if !hasTo { + return fmt.Errorf("failed to set To address not has") + } + // 邮件主题 + message.Subject(title) + // 邮件HTML内容 + message.SetBodyString(mail.TypeTextHTML, htmlStr) + // 连接到邮件SMTP服务器 + client, err := mail.NewClient(smtp, + mail.WithSMTPAuth(mail.SMTPAuthAutoDiscover), + mail.WithUsername(user), + mail.WithPort(int(port)), + mail.WithPassword(password), + mail.WithTLSConfig(&tls.Config{InsecureSkipVerify: true}), + ) + if err != nil { + return fmt.Errorf("failed to create mail client: %s", err) + } + // 发送 + if err := client.DialAndSend(message); err != nil { + return fmt.Errorf("failed to send mail: %s", err) + } + return nil +} diff --git a/src/modules/notification/service/smsc.go b/src/modules/notification/service/smsc.go new file mode 100644 index 00000000..3af10b03 --- /dev/null +++ b/src/modules/notification/service/smsc.go @@ -0,0 +1,164 @@ +package service + +import ( + "bytes" + "fmt" + "strings" + tt "text/template" + "time" + + "github.com/linxGnu/gosmpp" + "github.com/linxGnu/gosmpp/data" + "github.com/linxGnu/gosmpp/pdu" + + "be.ems/src/framework/config" + "be.ems/src/framework/logger" + "be.ems/src/framework/utils/date" + "be.ems/src/framework/utils/parse" + neDataModel "be.ems/src/modules/network_data/model" +) + +var smscSession *gosmpp.Session + +// connSM 连接smsc +func connSM() *gosmpp.Session { + if smscSession != nil { + return smscSession + } + smscAddr := fmt.Sprint(config.Get("alarm.alarmSMSForward.smscAddr")) + systemType := fmt.Sprint(config.Get("alarm.alarmSMSForward.systemType")) + systemID := fmt.Sprint(config.Get("alarm.alarmSMSForward.systemID")) + password := fmt.Sprint(config.Get("alarm.alarmSMSForward.password")) + // 建立连接 + session, err := gosmpp.NewSession( + gosmpp.TXConnector(gosmpp.NonTLSDialer, gosmpp.Auth{ + SMSC: smscAddr, + SystemID: systemID, + Password: password, + SystemType: systemType, + }), + gosmpp.Settings{ + ReadTimeout: 2 * time.Second, + OnSubmitError: func(_ pdu.PDU, err error) { + logger.Errorf("failed to smpp submit error: %s", err.Error()) + }, + OnRebindingError: func(err error) { + logger.Errorf("failed to smpp rebinding error: %s", err.Error()) + }, + }, -1) + if err != nil { + logger.Errorf("failed to create smpp new session error: %s", err.Error()) + return nil + } + // defer session.Close() + smscSession = session + return smscSession +} + +// newSubmitSM 构建短信提交 +func newSubmitSM(destSM string, message string) (*pdu.SubmitSM, error) { + dataCoding := parse.Number(config.Get("alarm.alarmSMSForward.dataCoding")) + enc := data.FromDataCoding(byte(dataCoding)) + srcSM := fmt.Sprint(config.Get("alarm.alarmSMSForward.serviceNumber")) + // 源地址 + srcAddr := pdu.NewAddress() + srcAddr.SetTon(5) + srcAddr.SetNpi(0) + err := srcAddr.SetAddress(srcSM) + if err != nil { + return nil, err + } + destAddr := pdu.NewAddress() + destAddr.SetTon(1) + destAddr.SetNpi(1) + err = destAddr.SetAddress(destSM) + if err != nil { + return nil, err + } + // build up submitSM + submitSM := pdu.NewSubmitSM().(*pdu.SubmitSM) + submitSM.SourceAddr = srcAddr + submitSM.DestAddr = destAddr + if err = submitSM.Message.SetMessageWithEncoding(message, enc); err != nil { + return nil, err + } + submitSM.ProtocolID = 0 + submitSM.RegisteredDelivery = 1 + submitSM.ReplaceIfPresentFlag = 0 + submitSM.EsmClass = 0 + return submitSM, nil +} + +// SMSCAlarm 发告警短信 +func SMSCAlarm(a neDataModel.Alarm, neIP string) error { + mobileList := fmt.Sprint(config.Get("alarm.alarmSMSForward.mobileList")) + if len(mobileList) == 0 { + return fmt.Errorf("smsc list is empty") + } + + // 模板 + textBodyTemplate := `Alarm Notification + Sequence: {{.AlarmSeq}}, + NE Name: {{.NeName}} + NE IP: {{.NeIp}} + Title: {{.AlarmTitle}} + Severity: {{.OrigSeverity}} + Event Time: {{.AlarmTime}} + Alarm Status: {{.AlarmStatus}} + Automatic sent by OMC, please do not reply!` + textTpl, err := tt.New("texttpl").Parse(textBodyTemplate) + if err != nil { + return fmt.Errorf("SMSCAlarm alarmId:%s fail %s", a.AlarmId, err.Error()) + } + // 参数值 + data := map[string]any{ + "AlarmSeq": a.AlarmSeq, + "NeName": a.NeName, + "NeIp": neIP, + "AlarmTitle": a.AlarmTitle, + "OrigSeverity": a.OrigSeverity, + "AlarmTime": date.ParseDateToStr(a.EventTime, time.RFC3339), + "AlarmStatus": a.AlarmStatus, + } + buffer := bytes.NewBuffer(nil) + if err := textTpl.Execute(buffer, data); err != nil { + return fmt.Errorf("EmailAlarm Execute alarmId:%s fail %s", a.AlarmId, err.Error()) + } + textStr := buffer.String() + + // 发送短信 + err = SMSCSendText(textStr, strings.Split(mobileList, ",")) + if err != nil { + return fmt.Errorf("EmailAlarm alarmId:%s fail %s", a.AlarmId, err.Error()) + } + return err +} + +// SMSCSendText 发送文本短信 +func SMSCSendText(textStr string, toMobileArr []string) error { + enable := parse.Boolean(config.Get("alarm.alarmSMSForward.enable")) + if !enable { + return fmt.Errorf("smsc notification not enable") + } + sm := connSM() + if sm == nil { + return fmt.Errorf("smpp new session create failed") + } + + errArr := []string{} + for _, v := range toMobileArr { + submitSM, err := newSubmitSM(v, textStr) + if err != nil { + errArr = append(errArr, err.Error()) + continue + } + if err = sm.Transceiver().Submit(submitSM); err != nil { + errArr = append(errArr, err.Error()) + continue + } + } + if len(errArr) > 0 { + return fmt.Errorf("%s", strings.Join(errArr, ",")) + } + return nil +} diff --git a/src/modules/oam/controller/api_rest.go b/src/modules/oam/controller/api_rest.go new file mode 100644 index 00000000..056ccb03 --- /dev/null +++ b/src/modules/oam/controller/api_rest.go @@ -0,0 +1,721 @@ +package controller + +import ( + "encoding/json" + "fmt" + "strings" + "time" + + "github.com/gin-gonic/gin" + "github.com/tsmask/go-oam" + goOamState "github.com/tsmask/go-oam/modules/state/service" + + "be.ems/src/framework/logger" + "be.ems/src/framework/resp" + "be.ems/src/framework/utils/date" + "be.ems/src/framework/utils/parse" + neFetchlink "be.ems/src/modules/network_element/fetch_link" + neModel "be.ems/src/modules/network_element/model" + neService "be.ems/src/modules/network_element/service" + oamService "be.ems/src/modules/oam/service" +) + +// NewAPIRest 实例化控制层 +var NewAPIRest = &APIRestController{} + +// APIRestController 北向定义 控制层处理 +// +// PATH /api/rest +type APIRestController struct{} + +// ResolveAlarm 接收告警 +// +// POST /faultManagement/v1/elementType/:elementTypeValue/objectType/alarms +func (s APIRestController) ResolveAlarm(c *gin.Context) { + var body []struct { + AlarmSeq int `json:"alarmSeq"` + AlarmId string `json:"alarmId"` + NeId string `json:"neId"` // 收到实际是rmUID + AlarmCode int `json:"alarmCode"` + AlarmTitle string `json:"alarmTitle"` + EventTime string `json:"eventTime"` + AlarmType string `json:"alarmType"` + OrigSeverity string `json:"origSeverity"` + PerceivedSeverity string `json:"perceivedSeverity"` + PVFlag string `json:"pvFlag"` + NeName string `json:"neName"` + NeType string `json:"neType"` + ObjectUid string `json:"objectUid"` + ObjectName string `json:"objectName"` + ObjectType string `json:"objectType"` + LocationInfo string `json:"locationInfo"` + Province string `json:"province"` + AlarmStatus int `json:"alarmStatus"` + SpecificProblem string `json:"specificProblem"` + SpecificProblemID string `json:"specificProblemID"` + AddInfo string `json:"addInfo"` + } + if err := c.ShouldBindBodyWithJSON(&body); err != nil { + errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err)) + c.JSON(422, resp.CodeMsg(resp.CODE_PARAM_PARSER, errMsgs)) + return + } + elementTypeValue := c.Param("elementTypeValue") + + // alarmTypeValue 映射值 + alarmTypeValue := func(str string) string { + arr := []string{ + oam.ALARM_TYPE_COMMUNICATION_ALARM, + oam.ALARM_TYPE_EQUIPMENT_ALARM, + oam.ALARM_TYPE_PROCESSING_FAILURE, + oam.ALARM_TYPE_ENVIRONMENTAL_ALARM, + oam.ALARM_TYPE_QUALITY_OF_SERVICE_ALARM, + } + for k, v := range arr { + if v == str { + return v + } + if fmt.Sprint(k+1) == str { + return v + } + } + return str + } + + // origSeverityValue 映射值 + origSeverityValue := func(str string) string { + arr := []string{ + oam.ALARM_SEVERITY_CRITICAL, + oam.ALARM_SEVERITY_MAJOR, + oam.ALARM_SEVERITY_MINOR, + oam.ALARM_SEVERITY_WARNING, + oam.ALARM_SEVERITY_EVENT, + } + for k, v := range arr { + if v == str { + return v + } + if fmt.Sprint(k+1) == str { + return v + } + } + return str + } + + // alarmStatusValue 映射值 + alarmStatusValue := func(value int) string { + arr := []string{ + oam.ALARM_STATUS_CLEAR, + oam.ALARM_STATUS_ACTIVE, + } + for k, v := range arr { + if k == value { + return v + } + } + return oam.ALARM_STATUS_ACTIVE + } + + alarmArr := make([]oam.Alarm, 0) + for _, v := range body { + if !strings.EqualFold(v.NeType, elementTypeValue) { + c.JSON(422, resp.CodeMsg(resp.CODE_PARAM_CHEACK, "elementType is inconsistent with neType")) + return + } + // 产生时间 + eventTime := date.ParseStrToDate(v.EventTime, time.RFC3339) + // 创建告警 + alarm := oam.Alarm{ + NeUid: v.NeId, // 网元唯一标识 + AlarmTime: eventTime.UnixMilli(), // 事件产生时间 + AlarmId: v.AlarmId, // 告警ID 唯一,清除时对应 + AlarmCode: v.AlarmCode, // 告警状态码 + AlarmType: alarmTypeValue(v.AlarmType), // 告警类型 + AlarmTitle: v.AlarmTitle, // 告警标题 + PerceivedSeverity: origSeverityValue(v.OrigSeverity), // 告警级别 + AlarmStatus: alarmStatusValue(v.AlarmStatus), // 告警状态 + SpecificProblem: v.SpecificProblem, // 告警问题原因 + SpecificProblemID: v.SpecificProblemID, // 告警问题原因ID + AddInfo: v.AddInfo, // 告警辅助信息 + LocationInfo: v.LocationInfo, // 告警定位信息 + } + alarmArr = append(alarmArr, alarm) + } + + errArr := make([]string, 0) + for _, alarm := range alarmArr { + if err := oamService.NewAlarm.Resolve(alarm); err != nil { + errArr = append(errArr, err.Error()) + } + } + + if len(errArr) > 0 { + c.JSON(200, resp.ErrData(errArr)) + return + } + c.JSON(200, resp.Ok(nil)) +} + +// ResolveCDR 接收话单 +// +// POST /cdrManagement/v1/elementType/:elementTypeValue/objectType/cdrEvent +func (s APIRestController) ResolveCDR(c *gin.Context) { + var body struct { + NeType string `json:"neType" ` + NeName string `json:"neName" ` + RmUID string `json:"rmUID" ` + Timestamp int `json:"timestamp" ` + CDR map[string]any `json:"CDR" ` + } + if err := c.ShouldBindBodyWithJSON(&body); err != nil { + errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err)) + c.JSON(422, resp.CodeMsg(resp.CODE_PARAM_PARSER, errMsgs)) + return + } + elementTypeValue := c.Param("elementTypeValue") + if !strings.EqualFold(body.NeType, elementTypeValue) { + c.JSON(422, resp.CodeMsg(resp.CODE_PARAM_CHEACK, "elementType is inconsistent with neType")) + return + } + + recordTime := time.Now() + if body.Timestamp > 1e12 { + recordTime = time.UnixMilli(int64(body.Timestamp)) + } else if body.Timestamp > 1e9 { + recordTime = time.Unix(int64(body.Timestamp), 0) + } + // 创建CDR + cdr := oam.CDR{ + NeUid: body.RmUID, // 网元唯一标识 + RecordTime: recordTime.UnixMilli(), // 记录时间 时间戳毫秒,Push时自动填充 + Data: body.CDR, // 话单信息 + } + if err := oamService.NewCDR.Resolve(cdr); err != nil { + c.JSON(200, resp.ErrMsg(err.Error())) + return + } + c.JSON(200, resp.Ok(nil)) +} + +// ResolveKPI 接收KPI +// +// POST /performanceManagement/v1/elementType/:elementTypeValue/objectType/kpiReport/:index +func (s APIRestController) ResolveKPI(c *gin.Context) { + var body struct { + Timestamp string `json:"TimeStamp" binding:"required"` + Task struct { + Period struct { + StartTime string `json:"StartTime"` + EndTime string `json:"EndTime"` + } `json:"Period" binding:"required"` + NE struct { + NEName string `json:"NEName"` + RmUID string `json:"rmUID"` + NeType string `json:"NeType"` + KPIs []struct { + KPIID string `json:"KPIID"` + Value int64 `json:"Value"` + Err string `json:"Err"` + } `json:"KPIs" binding:"required"` + } `json:"NE" binding:"required"` + } `json:"Task" binding:"required"` + } + if err := c.ShouldBindBodyWithJSON(&body); err != nil { + errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err)) + c.JSON(422, resp.CodeMsg(resp.CODE_PARAM_PARSER, errMsgs)) + return + } + elementTypeValue := c.Param("elementTypeValue") + if !strings.EqualFold(body.Task.NE.NeType, elementTypeValue) { + c.JSON(422, resp.CodeMsg(resp.CODE_PARAM_CHEACK, "elementType is inconsistent with neType")) + return + } + // index := c.Param("index") + + timestamp := body.Timestamp + taskPeriod := body.Task.Period + taskNeKPIs := body.Task.NE.KPIs + // 时间数据处理 + receiverTime := date.ParseStrToDate(timestamp, date.YYYY_MM_DDTHH_MM_SSZ) + startTime := date.ParseStrToDate(taskPeriod.StartTime, date.YYYY_MM_DDTHH_MM_SSZ) + endTime := date.ParseStrToDate(taskPeriod.EndTime, date.YYYY_MM_DDTHH_MM_SSZ) + granularity := parse.Number(endTime.Sub(startTime).Seconds()) + // kpi data数据 + KpiValues := make(map[string]float64, 0) + for _, v := range taskNeKPIs { + KpiValues[v.KPIID] = float64(v.Value) + } + + // 创建KPI + kpi := oam.KPI{ + NeUid: body.Task.NE.RmUID, // 网元唯一标识 + RecordTime: receiverTime.UnixMilli(), // 记录时间 时间戳毫秒,Push时自动填充 + Granularity: granularity, // 时间间隔 5/10/.../60/300 (秒) + Data: KpiValues, // 指标信息 + } + if err := oamService.NewKPI.Resolve(kpi); err != nil { + c.JSON(200, resp.ErrMsg(err.Error())) + return + } + c.JSON(200, resp.Ok(nil)) +} + +// ResolveNBState 接收基站状态变更 +// +// POST /ueManagement/v1/elementType/:elementTypeValue/objectType/nbState +func (s APIRestController) ResolveNBState(c *gin.Context) { + var body struct { + NeType string `json:"neType" ` + NeName string `json:"neName" ` + RmUID string `json:"rmUID"` + StateList []struct { + Address string `json:"address" ` + Name string `json:"name" ` + Position string `json:"position" ` + NbName string `json:"nbName" ` + State string `json:"state" ` // "OFF" or "ON" + OffTime string `json:"offTime" ` //if State=OFF, will set it + OnTime string `json:"onTime" ` //if State=ON , will set it + } + } + if err := c.ShouldBindBodyWithJSON(&body); err != nil { + errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err)) + c.JSON(422, resp.CodeMsg(resp.CODE_PARAM_PARSER, errMsgs)) + return + } + elementTypeValue := c.Param("elementTypeValue") + if !strings.EqualFold(body.NeType, elementTypeValue) { + c.JSON(422, resp.CodeMsg(resp.CODE_PARAM_CHEACK, "elementType is inconsistent with neType")) + return + } + + if len(body.StateList) == 0 { + c.JSON(422, resp.CodeMsg(resp.CODE_PARAM_CHEACK, "no stateList")) + return + } + + nbStateArr := make([]oam.NBState, 0) + for _, v := range body.StateList { + if v.Address == "" || v.State == "" { + continue + } + stateTime := date.ParseStrToDate(v.OffTime, time.RFC3339) + stateStr := oam.NB_STATE_OFF + if v.State == "ON" { + stateTime = date.ParseStrToDate(v.OnTime, time.RFC3339) + stateStr = oam.NB_STATE_ON + } + + // 创建NbState + nbState := oam.NBState{ + NeUid: body.RmUID, // 网元唯一标识 + RecordTime: time.Now().UnixMilli(), // 记录时间 时间戳毫秒,Push时自动填充 + Address: v.Address, // 基站地址 + DeviceName: v.NbName, // 基站设备名称 + State: stateStr, // 基站状态 ON/OFF + StateTime: stateTime.UnixMilli(), // 基站状态时间 时间戳毫秒 + Name: v.Name, // 基站名称 网元标记 + Position: v.Position, // 基站位置 网元标记 + } + nbStateArr = append(nbStateArr, nbState) + } + + errArr := make([]string, 0) + for _, nbState := range nbStateArr { + if err := oamService.NewNBState.Resolve(nbState); err != nil { + errArr = append(errArr, err.Error()) + } + } + + if len(errArr) > 0 { + c.JSON(200, resp.ErrData(errArr)) + return + } + c.JSON(200, resp.Ok(nil)) +} + +// ResolveUENB 接收终端接入基站 +// +// POST /logManagement/v1/elementType/:elementTypeValue/objectType/ueEvent +func (s APIRestController) ResolveUENB(c *gin.Context) { + var body struct { + NeType string `json:"neType" ` + NeName string `json:"neName" ` + RmUID string `json:"rmUID" ` + Timestamp int64 `json:"timestamp" ` + EventType string `json:"eventType" ` + EventJson map[string]any `json:"eventJSON" ` + } + if err := c.ShouldBindBodyWithJSON(&body); err != nil { + errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err)) + c.JSON(422, resp.CodeMsg(resp.CODE_PARAM_PARSER, errMsgs)) + return + } + elementTypeValue := c.Param("elementTypeValue") + if !strings.EqualFold(body.NeType, elementTypeValue) { + c.JSON(422, resp.CodeMsg(resp.CODE_PARAM_CHEACK, "elementType is inconsistent with neType")) + return + } + + // 记录时间 + recordTime := time.Now() + if body.Timestamp > 1e12 { + recordTime = time.UnixMilli(int64(body.Timestamp)) + } else if body.Timestamp > 1e9 { + recordTime = time.Unix(int64(body.Timestamp), 0) + } + + // 创建UENB + uenb := oam.UENB{ + NeUid: body.RmUID, // 网元唯一标识 + RecordTime: recordTime.UnixMilli(), // 记录时间 + NBId: "0", // 基站ID + CellId: "0", // 小区ID + TAC: "", // TAC + IMSI: "", // IMSI + Result: oam.UENB_RESULT_AUTH_SUCCESS, // 结果值 + Type: oam.UENB_TYPE_DETACH, // 终端接入基站类型 + } + + // 基站ID + if v, ok := body.EventJson["eNBID"]; ok && v != nil { + uenb.NBId = fmt.Sprint(v) + } + if v, ok := body.EventJson["gNBID"]; ok && v != nil { + uenb.NBId = fmt.Sprint(v) + } + // 小区ID + if v, ok := body.EventJson["cellID"]; ok && v != nil { + uenb.CellId = fmt.Sprint(v) + } + // TAC + if v, ok := body.EventJson["tacID"]; ok && v != nil { + uenb.TAC = fmt.Sprint(v) + } + // IMSI + if v, ok := body.EventJson["imsi"]; ok && v != nil { + uenb.IMSI = fmt.Sprint(v) + } + // 结果值 + if v, ok := body.EventJson["result"]; ok && v != nil { + uenb.Result = fmt.Sprint(v) + } + // 终端接入基站类型 + if v, ok := body.EventJson["type"]; ok && v != nil { + switch v := fmt.Sprint(v); v { + case "detach": + uenb.Type = oam.UENB_TYPE_DETACH + case "auth-result": + uenb.Type = oam.UENB_TYPE_AUTH + case "cm-state": + uenb.Type = oam.UENB_TYPE_CM + } + } + + if err := oamService.NewUENB.Resolve(uenb); err != nil { + c.JSON(200, resp.ErrMsg(err.Error())) + return + } + c.JSON(200, resp.Ok(nil)) +} + +// ResolveUENBByAMF 接收终端接入基站-AMF +// +// POST /upload-ue/v1/:eventType +func (s APIRestController) ResolveUENBByAMF(c *gin.Context) { + var body map[string]any + if err := c.ShouldBindBodyWithJSON(&body); err != nil { + errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err)) + c.JSON(422, resp.CodeMsg(resp.CODE_PARAM_PARSER, errMsgs)) + return + } + + // 创建UENB + uenb := oam.UENB{ + NeUid: "4400HXAMF001", // 网元唯一标识 + RecordTime: 0, // 记录时间 + NBId: "0", // 基站ID + CellId: "0", // 小区ID + TAC: "", // TAC + IMSI: "", // IMSI + Result: oam.UENB_RESULT_AUTH_SUCCESS, // 结果值 + Type: oam.UENB_TYPE_DETACH, // 终端接入基站类型 + } + + // 从eventJson中获取rmUID + if v, ok := body["rmUID"]; ok { + uenb.NeUid = fmt.Sprint(v) + } + + // 统一格式 + eventType := c.Param("eventType") + switch eventType { + case "auth-result": + // {"authCode":"200","authMessage":"成功","authTime":"2024-12-07 16:48:37","cellID":"3","gNBID":"1","imsi":"460002082100000","onlineNumber":1,"tacID":"81"} + if v, ok := body["imsi"]; ok { + uenb.IMSI = fmt.Sprint(v) + } + if v, ok := body["cellID"]; ok { + uenb.CellId = fmt.Sprint(v) + } + if v, ok := body["gNBID"]; ok { + uenb.NBId = fmt.Sprint(v) + } + if v, ok := body["tacID"]; ok { + uenb.TAC = fmt.Sprint(v) + } + + if v, ok := body["authCode"]; ok { + uenb.Result = fmt.Sprint(v) + } + if v, ok := body["authTime"]; ok { + authTime := date.ParseStrToDate(fmt.Sprint(v), date.YYYY_MM_DD_HH_MM_SS) + uenb.RecordTime = authTime.UnixMilli() + } + uenb.Type = oam.UENB_TYPE_AUTH + case "detach": + // {"detachResult":0,"detachTime":"2024-12-07 18:00:47","imsi":"460002082100000"} + if v, ok := body["imsi"]; ok { + uenb.IMSI = fmt.Sprint(v) + } + if v, ok := body["detachResult"]; ok { + if v == "0" { + uenb.Result = oam.UENB_RESULT_AUTH_SUCCESS + } else { + uenb.Result = fmt.Sprint(v) + } + } + if v, ok := body["detachTime"]; ok { + detachTime := date.ParseStrToDate(fmt.Sprint(v), date.YYYY_MM_DD_HH_MM_SS) + uenb.RecordTime = detachTime.UnixMilli() + } + uenb.Type = oam.UENB_TYPE_DETACH + case "cm-state": + // {"changeTime":"2024-12-07 17:07:52","imsi":"460002082100000","onlineNumber":1,"status":2} + if v, ok := body["imsi"]; ok { + uenb.IMSI = fmt.Sprint(v) + } + if v, ok := body["status"]; ok { + uenb.Result = fmt.Sprint(v) + } + if v, ok := body["changeTime"]; ok { + changeTime := date.ParseStrToDate(fmt.Sprint(v), date.YYYY_MM_DD_HH_MM_SS) + uenb.RecordTime = changeTime.UnixMilli() + } + uenb.Type = oam.UENB_TYPE_CM + } + + if err := oamService.NewUENB.Resolve(uenb); err != nil { + c.JSON(200, resp.ErrMsg(err.Error())) + return + } + c.JSON(200, resp.Ok(nil)) +} + +// ResolveAlarmHistory 拉取告警历史 +// +// GET /faultManagement/v1/elementType/:elementTypeValue/objectType/alarms +func (s APIRestController) ResolveAlarmHistory(c *gin.Context) { + elementTypeValue := c.Param("elementTypeValue") + + // Get alarms from OMC return 204 + if strings.ToLower(elementTypeValue) == "omc" { + c.JSON(200, resp.OkMsg("omc alarms no content")) + return + } + + // alarmTypeValue 映射值 + alarmTypeValue := func(str string) string { + arr := []string{ + oam.ALARM_TYPE_COMMUNICATION_ALARM, + oam.ALARM_TYPE_EQUIPMENT_ALARM, + oam.ALARM_TYPE_PROCESSING_FAILURE, + oam.ALARM_TYPE_ENVIRONMENTAL_ALARM, + oam.ALARM_TYPE_QUALITY_OF_SERVICE_ALARM, + } + for k, v := range arr { + if v == str { + return v + } + if fmt.Sprint(k+1) == str { + return v + } + } + return str + } + + // origSeverityValue 映射值 + origSeverityValue := func(str string) string { + arr := []string{ + oam.ALARM_SEVERITY_CRITICAL, + oam.ALARM_SEVERITY_MAJOR, + oam.ALARM_SEVERITY_MINOR, + oam.ALARM_SEVERITY_WARNING, + oam.ALARM_SEVERITY_EVENT, + } + for k, v := range arr { + if v == str { + return v + } + if fmt.Sprint(k+1) == str { + return v + } + } + return str + } + + // alarmStatusValue 映射值 + alarmStatusValue := func(value int) string { + arr := []string{ + oam.ALARM_STATUS_CLEAR, + oam.ALARM_STATUS_ACTIVE, + } + for k, v := range arr { + if k == value { + return v + } + } + return oam.ALARM_STATUS_ACTIVE + } + + alarmArr := make([]oam.Alarm, 0) + type body struct { + AlarmSeq int `json:"alarmSeq"` + AlarmId string `json:"alarmId"` + NeId string `json:"neId"` // 收到实际是rmUID + AlarmCode int `json:"alarmCode"` + AlarmTitle string `json:"alarmTitle"` + EventTime string `json:"eventTime"` + AlarmType string `json:"alarmType"` + OrigSeverity string `json:"origSeverity"` + PerceivedSeverity string `json:"perceivedSeverity"` + PVFlag string `json:"pvFlag"` + NeName string `json:"neName"` + NeType string `json:"neType"` + ObjectUid string `json:"objectUid"` + ObjectName string `json:"objectName"` + ObjectType string `json:"objectType"` + LocationInfo string `json:"locationInfo"` + Province string `json:"province"` + AlarmStatus int `json:"alarmStatus"` + SpecificProblem string `json:"specificProblem"` + SpecificProblemID string `json:"specificProblemID"` + AddInfo string `json:"addInfo"` + } + parseItem := func(v body) oam.Alarm { + // 产生时间 + eventTime := date.ParseStrToDate(v.EventTime, time.RFC3339) + // 创建告警 + alarm := oam.Alarm{ + NeUid: v.NeId, // 网元唯一标识 + AlarmTime: eventTime.UnixMilli(), // 事件产生时间 + AlarmId: v.AlarmId, // 告警ID 唯一,清除时对应 + AlarmCode: v.AlarmCode, // 告警状态码 + AlarmType: alarmTypeValue(v.AlarmType), // 告警类型 + AlarmTitle: v.AlarmTitle, // 告警标题 + PerceivedSeverity: origSeverityValue(v.OrigSeverity), // 告警级别 + AlarmStatus: alarmStatusValue(v.AlarmStatus), // 告警状态 + SpecificProblem: v.SpecificProblem, // 告警问题原因 + SpecificProblemID: v.SpecificProblemID, // 告警问题原因ID + AddInfo: v.AddInfo, // 告警辅助信息 + LocationInfo: v.LocationInfo, // 告警定位信息 + } + return alarm + } + var neInfos []neModel.NeInfo + if elementTypeValue == "all" { + neInfos = neService.NewNeInfo.Find(neModel.NeInfo{}, false, false) + } else { + neInfos = neService.NewNeInfo.FindByNeType(strings.ToUpper(elementTypeValue)) + } + for _, neInfo := range neInfos { + data, err := neFetchlink.AlarmHistory(neInfo) + if err != nil { + logger.Errorf("failed to fetch alarm history:%s", err.Error()) + continue + } + if len(data) == 0 { + logger.Warnf("not found sync alarms %s", neInfo.RmUID) + continue + } + + bodyArr := make([]body, 0) + // 将 []map[string]any 序列化为 JSON 字符串 + jsonData, err := json.Marshal(data) + if err != nil { + logger.Errorf("marshal error: %s", err.Error()) + continue + } + // 反序列化到结构体 + err = json.Unmarshal(jsonData, &bodyArr) + if err != nil { + logger.Errorf("Error unmarshal error: %s", err.Error()) + continue + } + + for _, v := range bodyArr { + alarmArr = append(alarmArr, parseItem(v)) + } + } + + errArr := make([]string, 0) + for _, alarm := range alarmArr { + if err := oamService.NewAlarm.Resolve(alarm); err != nil { + errArr = append(errArr, err.Error()) + } + } + + if len(errArr) > 0 { + c.JSON(200, resp.OkData(errArr)) + return + } + c.JSON(200, resp.Ok(nil)) +} + +// QuerySystemState 查询系统状态 +// +// GET /systemManagement/v1/elementType/:elementTypeValue/objectType/systemState +func (s APIRestController) QuerySystemState(c *gin.Context) { + elementTypeValue := c.Param("elementTypeValue") + if strings.ToLower(elementTypeValue) != "omc" { + c.JSON(200, resp.ErrMsg("elementType only omc")) + return + } + info := goOamState.NewState.Info() + info.SerialNum = "-" + info.ExpiryDate = "-" + info.Capability = 50 + info.Version = "config.Version" + c.JSON(200, info) +} + +// NeConfigOMC 网元配置对端网管信息 +// +// PUT /systemManagement/v1/elementType/:elementTypeValue/objectType/config/omcNeConfig +func (s APIRestController) NeConfigOMC(c *gin.Context) { + c.JSON(204, nil) +} + +// @Description CBSManagement CB消息 +type CBSState struct { + NeName string `json:"neName"` // 网元名称 + RmUID string `json:"rmUID"` // 网元唯一标识 + EventData []oamService.CBSEventData `json:"eventData"` // 事件数据 +} + +func (s APIRestController) ResolveCBSState(c *gin.Context) { + var state CBSState + if err := c.ShouldBindBodyWithJSON(&state); err != nil { + errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err)) + c.JSON(422, resp.CodeMsg(resp.CODE_PARAM_PARSER, errMsgs)) + return + } + + for _, eventData := range state.EventData { + if err := oamService.NewCBS.Resolve(eventData); err != nil { + c.JSON(200, resp.ErrMsg(err.Error())) + return + } + } + c.JSON(200, resp.Ok(nil)) +} diff --git a/src/modules/oam/oam.go b/src/modules/oam/oam.go new file mode 100644 index 00000000..4435d7fb --- /dev/null +++ b/src/modules/oam/oam.go @@ -0,0 +1,42 @@ +package oam + +import ( + "github.com/gin-gonic/gin" + "github.com/tsmask/go-oam" + + "be.ems/src/framework/logger" + "be.ems/src/modules/oam/service" +) + +// Setup 模块路由注册 +func Setup(router *gin.Engine) { + logger.Infof("开始加载 ====> oam 模块路由") + + // 网管接收端收告警 + oam.AlarmReceiveRoute(router, service.NewAlarm.Resolve) + // 网管接收端收终端接入基站 + oam.UENBReceiveRoute(router, service.NewUENB.Resolve) + // 网管接收端收基站状态 + oam.NBStateReceiveRoute(router, service.NewNBState.Resolve) + // 网管接收端收话单 + oam.CDRReceiveRoute(router, service.NewCDR.Resolve) + // 网管接收端收KPI + oam.KPIReceiveRoute(router, service.NewKPI.Resolve) + + // APIRest 北向定义 + // aprRest := controller.NewAPIRest + // aprRestGroup := router.Group("/api/rest") + // { + // aprRestGroup.GET("/faultManagement/v1/elementType/:elementTypeValue/objectType/alarms", aprRest.ResolveAlarmHistory) + // aprRestGroup.POST("/faultManagement/v1/elementType/:elementTypeValue/objectType/alarms", aprRest.ResolveAlarm) + // aprRestGroup.POST("/cdrManagement/v1/elementType/:elementTypeValue/objectType/cdrEvent", aprRest.ResolveCDR) + // aprRestGroup.POST("/performanceManagement/v1/elementType/:elementTypeValue/objectType/kpiReport/:index", aprRest.ResolveKPI) + // aprRestGroup.POST("/ueManagement/v1/elementType/:elementTypeValue/objectType/nbState", aprRest.ResolveNBState) + // aprRestGroup.POST("/ueManagement/v1/elementType/:elementTypeValue/objectType/cbsState", aprRest.ResolveCBSState) + // aprRestGroup.POST("/logManagement/v1/elementType/:elementTypeValue/objectType/ueEvent", aprRest.ResolveUENB) + // router.POST("/upload-ue/v1/:eventType", aprRest.ResolveUENBByAMF) // AMF特殊上报 + // aprRestGroup.GET("/systemManagement/v1/elementType/:elementTypeValue/objectType/systemState", aprRest.QuerySystemState) + // aprRestGroup.PUT("/systemManagement/v1/elementType/:elementTypeValue/objectType/config/omcNeConfig", aprRest.NeConfigOMC) + // } + +} diff --git a/src/modules/oam/service/alarm.go b/src/modules/oam/service/alarm.go new file mode 100644 index 00000000..4afb35f8 --- /dev/null +++ b/src/modules/oam/service/alarm.go @@ -0,0 +1,301 @@ +package service + +import ( + "fmt" + "time" + + "be.ems/src/framework/config" + "be.ems/src/framework/constants" + "be.ems/src/framework/utils/parse" + "github.com/tsmask/go-oam" + + neDataModel "be.ems/src/modules/network_data/model" + neDataService "be.ems/src/modules/network_data/service" + neService "be.ems/src/modules/network_element/service" + notificationService "be.ems/src/modules/notification/service" + traceService "be.ems/src/modules/trace/service" + wsService "be.ems/src/modules/ws/service" +) + +// 实例化服务层 Alarm 结构体 +var NewAlarm = &Alarm{ + neInfoService: neService.NewNeInfo, + wsService: wsService.NewWSSend, + alarmService: neDataService.NewAlarm, + alarmEventService: neDataService.NewAlarmEvent, + alarmLogService: neDataService.NewAlarmLog, + alarmForwardLogService: neDataService.NewAlarmForwardLog, +} + +// Alarm 消息处理 +type Alarm struct { + neInfoService *neService.NeInfo + wsService *wsService.WSSend + alarmService *neDataService.Alarm + alarmEventService *neDataService.AlarmEvent + alarmLogService *neDataService.AlarmLog + alarmForwardLogService *neDataService.AlarmForwardLog +} + +// Resolve 接收处理 +func (s *Alarm) Resolve(a oam.Alarm) error { + // 是否存在网元 + neInfo := s.neInfoService.FindByRmuid(a.NeUid) + if neInfo.NeType == "" || neInfo.RmUID != a.NeUid { + return fmt.Errorf("resolve alarm network element does not exist %s", a.NeUid) + } + + // seq 告警序号 + lastSeq := neDataService.NewAlarm.FindAlarmSeqLast(neInfo.NeType, neInfo.NeId) + + alarmTime := time.UnixMilli(a.AlarmTime) + // 告警信息 + alarm := neDataModel.Alarm{ + NeType: neInfo.NeType, + NeId: neInfo.NeId, + NeName: neInfo.NeName, + Province: neInfo.Province, + PvFlag: neInfo.PvFlag, + AlarmSeq: fmt.Sprintf("%d", lastSeq+1), + AlarmId: a.AlarmId, + AlarmTitle: a.AlarmTitle, + AlarmCode: fmt.Sprintf("%d", a.AlarmCode), + EventTime: alarmTime, + AlarmType: a.AlarmType, + OrigSeverity: a.PerceivedSeverity, + PerceivedSeverity: a.PerceivedSeverity, + ObjectUid: neInfo.RmUID, + ObjectName: neInfo.NeName, + ObjectType: neInfo.NeType, + LocationInfo: a.LocationInfo, + AlarmStatus: a.AlarmStatus, + SpecificProblem: a.SpecificProblem, + SpecificProblemId: a.SpecificProblemID, + AddInfo: a.AddInfo, + } + + // 进行清除 + if a.AlarmStatus == oam.ALARM_STATUS_CLEAR { + if a.PerceivedSeverity == oam.ALARM_SEVERITY_EVENT { + if err := s.clearEvent(alarm); err != nil { + return err + } + } else { + if err := s.clear(alarm); err != nil { + return err + } + } + + } + // 进行新增 + if a.AlarmStatus == oam.ALARM_STATUS_ACTIVE { + if a.PerceivedSeverity == oam.ALARM_SEVERITY_EVENT { + if err := s.addEvent(alarm); err != nil { + return err + } + } else { + if err := s.add(alarm); err != nil { + return err + } + } + } + + // 记录日志 + if err := s.saveLog(alarm); err != nil { + return err + } + // 推送 + s.wsService.ByGroupID(fmt.Sprintf("%s_%s_%s", wsService.GROUP_ALARM, neInfo.NeType, neInfo.NeId), alarm) + // 通知 + go s.notify(neInfo.IP, alarm) + return nil +} + +// saveLog 记录日志 +func (s *Alarm) saveLog(alarm neDataModel.Alarm) error { + alarmLog := neDataModel.AlarmLog{ + NeType: alarm.NeType, + NeId: alarm.NeId, + AlarmSeq: alarm.AlarmSeq, + AlarmId: alarm.AlarmId, + AlarmTitle: alarm.AlarmTitle, + AlarmCode: alarm.AlarmCode, + AlarmStatus: alarm.AlarmStatus, + AlarmType: alarm.AlarmType, + OrigSeverity: alarm.PerceivedSeverity, + EventTime: alarm.EventTime, + } + insertId := s.alarmLogService.Insert(alarmLog) + if insertId <= 0 { + return fmt.Errorf("save alarm log fail") + } + return nil +} + +// add 新增告警 +func (s *Alarm) add(alarm neDataModel.Alarm) error { + // 检查网元告警ID是否唯一 + alarmIdArr := s.alarmService.Find(neDataModel.Alarm{ + NeType: alarm.NeType, + NeId: alarm.NeId, + AlarmId: alarm.AlarmId, + }) + if len(alarmIdArr) > 0 { + return fmt.Errorf("already exists alarmId:%s", alarm.AlarmId) + } + insertId := s.alarmService.Insert(alarm) + if insertId != "" { + alarm.ID = insertId + return nil + } + return fmt.Errorf("add alarm fail") +} + +// clear 清除告警 +func (s *Alarm) clear(alarm neDataModel.Alarm) error { + // 检查网元告警ID是否唯一 + alarmIdArr := s.alarmService.Find(neDataModel.Alarm{ + NeType: alarm.NeType, + NeId: alarm.NeId, + AlarmId: alarm.AlarmId, + }) + if len(alarmIdArr) != 1 { + return fmt.Errorf("not exists alarmId:%s", alarm.AlarmId) + } + + // 告警清除 + rows, _ := s.alarmService.ClearByIds([]string{alarmIdArr[0].ID}, alarm.ObjectUid, constants.ALARM_CLEAR_TYPE_AUTO_CLEAR) + if rows > 0 { + return nil + } + return fmt.Errorf("clear fail alarmId:%s", alarm.AlarmId) +} + +// addEvent 新增告警事件 +func (s *Alarm) addEvent(alarm neDataModel.Alarm) error { + // 检查网元告警ID是否唯一 + alarmIdArr := s.alarmEventService.Find(neDataModel.AlarmEvent{ + NeType: alarm.NeType, + NeId: alarm.NeId, + AlarmId: alarm.AlarmId, + }) + if len(alarmIdArr) > 0 { + return fmt.Errorf("event already exists alarmId:%s", alarm.AlarmId) + } + // seq 告警序号 + lastSeq := s.alarmEventService.FindAlarmEventSeqLast(alarm.NeType, alarm.NeId) + + alarmEvent := neDataModel.AlarmEvent{ + NeType: alarm.NeType, + NeId: alarm.NeId, + AlarmSeq: fmt.Sprintf("%d", lastSeq+1), + AlarmId: alarm.AlarmId, + AlarmTitle: alarm.AlarmTitle, + AlarmCode: alarm.AlarmCode, + EventTime: alarm.EventTime, + ObjectUid: alarm.ObjectUid, + ObjectName: alarm.ObjectName, + ObjectType: alarm.ObjectType, + LocationInfo: alarm.LocationInfo, + AlarmStatus: alarm.AlarmStatus, + SpecificProblem: alarm.SpecificProblem, + SpecificProblemId: alarm.SpecificProblemId, + AddInfo: alarm.AddInfo, + } + insertId := s.alarmEventService.Insert(alarmEvent) + if insertId > 0 { + alarmEvent.ID = insertId + // 网元重启后,清除活动告警 + if alarm.AlarmCode == fmt.Sprintf("%d", constants.ALARM_EVENT_REBOOT) { + rows := s.alarmService.Find(neDataModel.Alarm{ + NeType: alarm.NeType, + NeId: alarm.NeId, + AlarmStatus: oam.ALARM_STATUS_ACTIVE, + }) + ids := make([]string, 0) + for _, v := range rows { + ids = append(ids, v.ID) + } + s.alarmService.ClearByIds(ids, alarm.ObjectUid, constants.ALARM_CLEAR_TYPE_AUTO_CLEAR) + } + // 网元重启后,有跟踪任务的需要重新补发启动任务 + if alarm.AlarmCode == fmt.Sprintf("%d", constants.ALARM_EVENT_REBOOT) { + traceService.NewTraceTask.RunUnstopped(alarm.NeType, alarm.NeId) + } + return nil + } + return fmt.Errorf("event add fail") +} + +// clearEvent 清除告警事件 +func (s *Alarm) clearEvent(alarm neDataModel.Alarm) error { + alarmEventService := neDataService.NewAlarmEvent + // 检查网元告警ID是否唯一 + alarmIdArr := alarmEventService.Find(neDataModel.AlarmEvent{ + NeType: alarm.NeType, + NeId: alarm.NeId, + AlarmId: alarm.AlarmId, + }) + if len(alarmIdArr) != 1 { + return fmt.Errorf("event not exists alarmId:%s", alarm.AlarmId) + } + + // 告警清除 + rows, _ := s.alarmEventService.ClearByIds([]int64{alarmIdArr[0].ID}, alarm.ObjectUid, constants.ALARM_CLEAR_TYPE_AUTO_CLEAR) + if rows > 0 { + return nil + } + return fmt.Errorf("event clear fail alarmId:%s", alarm.AlarmId) +} + +// notify 通知 +func (s *Alarm) notify(neIp string, alarm neDataModel.Alarm) { + // 邮箱 + emailEnable := parse.Boolean(config.Get("notification.email.enable")) + if emailEnable { + emailList := fmt.Sprint(config.Get("notification.email.emailList")) + emailResult := "Sent Successfully!" + emailErr := notificationService.EmailAlarm(alarm, neIp) + if emailErr != nil { + emailResult = emailErr.Error() + } + s.notifyLog(alarm, "EMAIL", emailList, emailResult) + } + + // 短信 + smscEnable := parse.Boolean(config.Get("notification.smsc.enable")) + if smscEnable { + mobileList := fmt.Sprint(config.Get("notification.smsc.mobileList")) + smscResult := "Sent Successfully!" + smscErr := notificationService.SMSCAlarm(alarm, neIp) + if smscErr != nil { + smscResult = smscErr.Error() + } + s.notifyLog(alarm, "SMSC", mobileList, smscResult) + } +} + +// notifyLog 通知日志 +func (s *Alarm) notifyLog(alarm neDataModel.Alarm, forwardBy, toUser, result string) error { + alarmForwardLog := neDataModel.AlarmForwardLog{ + NeType: alarm.NeType, + NeId: alarm.NeId, + AlarmSeq: alarm.AlarmSeq, + AlarmId: alarm.AlarmId, + AlarmTitle: alarm.AlarmTitle, + AlarmCode: alarm.AlarmCode, + AlarmStatus: alarm.AlarmStatus, + AlarmType: alarm.AlarmType, + OrigSeverity: alarm.OrigSeverity, + EventTime: alarm.EventTime, + Type: forwardBy, + Target: toUser, + Result: result, + } + // 记录日志 + insertId := s.alarmForwardLogService.Insert(alarmForwardLog) + if insertId <= 0 { + return fmt.Errorf("notify alarm log fail") + } + return nil +} diff --git a/src/modules/oam/service/cbs_state.go b/src/modules/oam/service/cbs_state.go new file mode 100644 index 00000000..3b898621 --- /dev/null +++ b/src/modules/oam/service/cbs_state.go @@ -0,0 +1,29 @@ +package service + +import ( + neDataService "be.ems/src/modules/network_data/service" + neService "be.ems/src/modules/network_element/service" +) + +// 实例化服务层 CDR 结构体 +var NewCBS = &CBS{ + neInfoService: neService.NewNeInfo, + cbcMessageService: neDataService.NewCBCMessage, +} + +// CDR 消息处理 +type CBS struct { + neInfoService *neService.NeInfo + cbcMessageService *neDataService.CBCMessage // CDR会话事件服务 +} + +type CBSEventData struct { + EventName string `json:"eventName"` // 事件名称 + MessageId int64 `json:"messageId"` // 消息ID + Detail string `json:"detail"` // 详情 +} + +// Resolve 接收处理 +func (s *CBS) Resolve(c CBSEventData) error { + return s.cbcMessageService.UpdateDetail(c.EventName, c.Detail) +} diff --git a/src/modules/oam/service/cdr.go b/src/modules/oam/service/cdr.go new file mode 100644 index 00000000..8b1e2427 --- /dev/null +++ b/src/modules/oam/service/cdr.go @@ -0,0 +1,71 @@ +package service + +import ( + "encoding/json" + "fmt" + + "github.com/tsmask/go-oam" + + neDataModel "be.ems/src/modules/network_data/model" + neDataService "be.ems/src/modules/network_data/service" + neService "be.ems/src/modules/network_element/service" + wsService "be.ems/src/modules/ws/service" +) + +// 实例化服务层 CDR 结构体 +var NewCDR = &CDR{ + neInfoService: neService.NewNeInfo, + wsService: wsService.NewWSSend, + cdrEventService: neDataService.NewCDREvent, +} + +// CDR 消息处理 +type CDR struct { + neInfoService *neService.NeInfo + wsService *wsService.WSSend + cdrEventService *neDataService.CDREvent // CDR会话事件服务 +} + +// Resolve 接收处理 +func (s *CDR) Resolve(c oam.CDR) error { + if c.Data == nil { + return fmt.Errorf("cdr data is nil") + } + // 是否存在网元 + neInfo := s.neInfoService.FindByRmuid(c.NeUid) + if neInfo.NeType == "" || neInfo.RmUID != c.NeUid { + return fmt.Errorf("resolve cdr network element does not exist %s", c.NeUid) + } + + cdrByte, _ := json.Marshal(c.Data) + cdrEvent := neDataModel.CDREvent{ + NeType: neInfo.NeType, + NeName: neInfo.NeName, + RmUid: neInfo.RmUID, + Timestamp: c.RecordTime, + CdrJson: string(cdrByte), + CreatedAt: c.RecordTime, + } + insertId := s.cdrEventService.Insert(cdrEvent) + if insertId <= 0 { + return fmt.Errorf("add cdr data fail") + } + cdrEvent.ID = insertId + + // 推送到ws订阅组 + switch neInfo.NeType { + case "IMS": + dataMap := c.Data.(map[string]any) + v, ok := dataMap["recordType"] + if ok && (v == "MOC" || v == "MTSM") { + s.wsService.ByGroupID(fmt.Sprintf("%s_%s", wsService.GROUP_IMS_CDR, neInfo.NeId), cdrEvent) + } + case "SMF": + s.wsService.ByGroupID(fmt.Sprintf("%s_%s", wsService.GROUP_SMF_CDR, neInfo.NeId), cdrEvent) + case "SMSC": + s.wsService.ByGroupID(fmt.Sprintf("%s_%s", wsService.GROUP_SMSC_CDR, neInfo.NeId), cdrEvent) + case "SGWC": + s.wsService.ByGroupID(fmt.Sprintf("%s_%s", wsService.GROUP_SGWC_CDR, neInfo.NeId), cdrEvent) + } + return nil +} diff --git a/src/modules/oam/service/kpi.go b/src/modules/oam/service/kpi.go new file mode 100644 index 00000000..c9f567bb --- /dev/null +++ b/src/modules/oam/service/kpi.go @@ -0,0 +1,241 @@ +package service + +import ( + "encoding/json" + "fmt" + "math" + "time" + + "be.ems/src/framework/utils/date" + "be.ems/src/framework/utils/expr" + "be.ems/src/framework/utils/parse" + "github.com/tsmask/go-oam" + + neDataModel "be.ems/src/modules/network_data/model" + neDataService "be.ems/src/modules/network_data/service" + neModel "be.ems/src/modules/network_element/model" + neService "be.ems/src/modules/network_element/service" + wsService "be.ems/src/modules/ws/service" +) + +// 实例化服务层 KPI 结构体 +var NewKPI = &KPI{ + neInfoService: neService.NewNeInfo, + wsService: wsService.NewWSSend, + kpiReportService: neDataService.NewKpiReport, + kpiCReportService: neDataService.NewKpiCReport, +} + +// KPI 消息处理 +type KPI struct { + neInfoService *neService.NeInfo + wsService *wsService.WSSend + kpiReportService *neDataService.KpiReport + kpiCReportService *neDataService.KpiCReport +} + +// Resolve 接收处理 +func (s *KPI) Resolve(k oam.KPI) error { + if len(k.Data) == 0 { + return fmt.Errorf("kpi data is nil") + } + // 是否存在网元 + neInfo := s.neInfoService.FindByRmuid(k.NeUid) + if neInfo.NeType == "" || neInfo.RmUID != k.NeUid { + return fmt.Errorf("resolve kpi network element does not exist %s", k.NeUid) + } + + // 时间片 + curTime := time.Now() + curSeconds := curTime.Hour()*3600 + curTime.Minute()*60 + curTime.Second() + index := int64(curSeconds) / k.Granularity + + if err := s.saveKPIData(neInfo, k, index); err != nil { + return err + } + if err := s.saveKPIDataC(neInfo, k, index); err != nil { + return err + } + return nil +} + +// saveKPIData 存储KPI数据并推送到ws订阅组 +func (s KPI) saveKPIData(neInfo neModel.NeInfo, k oam.KPI, index int64) error { + // 时间数据处理 + recordTime := time.Now() + if k.RecordTime > 1e12 { + recordTime = time.UnixMilli(k.RecordTime) + } else if k.RecordTime > 1e9 { + recordTime = time.Unix(k.RecordTime, 0) + } + recordDate := date.ParseDateToStr(recordTime, "2006-01-02") + recordEndTime := date.ParseDateToStr(recordTime, "15:04:05") + startTime := recordTime.Add(-time.Duration(k.Granularity) * time.Second) + recordStartTime := date.ParseDateToStr(startTime, "15:04:05") + + // kpi data数据json + kpiTitles := s.kpiReportService.FindTitle(neInfo.NeType) + KpiValues := make([]map[string]any, 0) + for _, kt := range kpiTitles { + item := map[string]any{ + "kpiId": kt.KpiId, + "value": 0, + "err": "", + } + // 匹配指标记录 + for k, v := range k.Data { + if k == kt.KpiId { + item["value"] = v + } + } + KpiValues = append(KpiValues, item) + } + + KpiValuesByte, err := json.Marshal(KpiValues) + if err != nil { + return err + } + + // KPI 信息 + kpiData := neDataModel.KpiReport{ + NeType: neInfo.NeType, + NeName: neInfo.NeName, + RmUid: neInfo.RmUID, + Date: recordDate, + StartTime: recordStartTime, + EndTime: recordEndTime, + Index: index, + Granularity: k.Granularity, + KpiValues: string(KpiValuesByte), + CreatedAt: k.RecordTime, + } + insertId := s.kpiReportService.Insert(kpiData) + if insertId <= 0 { + return fmt.Errorf("add kpi data fail") + } + kpiData.ID = insertId + + // 指标事件对象 + data := map[string]any{ + "neType": kpiData.NeType, + "neName": kpiData.NeName, + "rmUID": kpiData.RmUid, + "startIndex": kpiData.Index, + "timeGroup": kpiData.CreatedAt, + // kip_id ... + } + for _, v := range KpiValues { + data[fmt.Sprint(v["kpiId"])] = v["value"] + } + + // 推送到ws订阅组 + s.wsService.ByGroupID(fmt.Sprintf("%s_%s_%s", wsService.GROUP_KPI, neInfo.NeType, neInfo.NeId), data) + // 更新UPF总流量 + if neInfo.NeType == "UPF" { + upValue := parse.Number(data["UPF.03"]) + downValue := parse.Number(data["UPF.06"]) + s.kpiReportService.UPFTodayFlowUpdate(neInfo.RmUID, upValue, downValue) + } + return nil +} + +// saveKPIDataC 存储自定义KPI数据并推送到ws订阅组 +func (s KPI) saveKPIDataC(neInfo neModel.NeInfo, k oam.KPI, index int64) error { + // 时间数据处理 + recordTime := time.Now() + if k.RecordTime > 1e12 { + recordTime = time.UnixMilli(k.RecordTime) + } else if k.RecordTime > 1e9 { + recordTime = time.Unix(k.RecordTime, 0) + } + recordDate := date.ParseDateToStr(recordTime, "2006-01-02") + recordEndTime := date.ParseDateToStr(recordTime, "15:04:05") + startTime := recordTime.Add(-time.Duration(k.Granularity) * time.Second) + recordStartTime := date.ParseDateToStr(startTime, "15:04:05") + + // kpi data数据json + kpiCTitles := s.kpiCReportService.FindTitle(neInfo.NeType) + KpiValues := make([]map[string]any, 0) + // 自定义指标的表达式环境变量 + KpiExprEnv := make(map[string]any, 0) + for k, v := range k.Data { + KpiExprEnv[k] = v + } + // 自定义指标的计算 + for _, v := range kpiCTitles { + item := map[string]any{ + "kpiId": v.KpiId, + "value": 0, + "err": "", + } + + // 匹配指标记录 + if envValue, envOk := KpiExprEnv[v.KpiId]; envOk { + item["value"] = envValue + } + + // 计算结果 + exprStr, exprEnv := expr.ParseExprEnv(v.Expression, KpiExprEnv) + result, err := expr.Eval(exprStr, exprEnv) + if err != nil { + item["value"] = 0 + item["err"] = err.Error() + } else { + if v.Unit == "%" { + resultV, ok := result.(float64) + if !ok || math.IsNaN(resultV) { + resultV = 0 + } + if resultV > 100 { + result = 100 + } + if resultV <= 0 { + result = 0 + } + } + + item["value"] = result + } + KpiValues = append(KpiValues, item) + } + KpiValuesByte, err := json.Marshal(KpiValues) + if err != nil { + return err + } + + // KPI 信息 + kpiCData := neDataModel.KpiCReport{ + NeType: neInfo.NeType, + NeName: neInfo.NeName, + RmUid: neInfo.RmUID, + Date: recordDate, + StartTime: recordStartTime, + EndTime: recordEndTime, + Index: index, + Granularity: k.Granularity, + KpiValues: string(KpiValuesByte), + CreatedAt: k.RecordTime, + } + insertId := s.kpiCReportService.Insert(kpiCData) + if insertId <= 0 { + return fmt.Errorf("add kpic data fail") + } + kpiCData.ID = insertId + + // 指标事件对象 + data := map[string]any{ + "neType": kpiCData.NeType, + "neName": kpiCData.NeName, + "rmUID": kpiCData.RmUid, + "startIndex": kpiCData.Index, + "timeGroup": kpiCData.CreatedAt, + // kip_id ... + } + for _, v := range KpiValues { + data[fmt.Sprint(v["kpiId"])] = v["value"] + } + + // 推送到ws订阅组 + s.wsService.ByGroupID(fmt.Sprintf("%s_%s_%s", wsService.GROUP_KPI_C, neInfo.NeType, neInfo.NeId), data) + return nil +} diff --git a/src/modules/oam/service/nb_state.go b/src/modules/oam/service/nb_state.go new file mode 100644 index 00000000..3f85a49f --- /dev/null +++ b/src/modules/oam/service/nb_state.go @@ -0,0 +1,65 @@ +package service + +import ( + "fmt" + "time" + + "be.ems/src/framework/utils/date" + "github.com/tsmask/go-oam" + + neDataModel "be.ems/src/modules/network_data/model" + neDataService "be.ems/src/modules/network_data/service" + neService "be.ems/src/modules/network_element/service" + wsService "be.ems/src/modules/ws/service" +) + +// 实例化服务层 NBState 结构体 +var NewNBState = &NBState{ + neInfoService: neService.NewNeInfo, + wsService: wsService.NewWSSend, + nbStateService: neDataService.NewNBState, +} + +// NBState 消息处理 +type NBState struct { + neInfoService *neService.NeInfo + wsService *wsService.WSSend + nbStateService *neDataService.NBState +} + +// Resolve 接收处理 +func (s *NBState) Resolve(n oam.NBState) error { + // 是否存在网元 + neInfo := s.neInfoService.FindByRmuid(n.NeUid) + if neInfo.NeType == "" || neInfo.RmUID != n.NeUid { + return fmt.Errorf("resolve nb_state network element does not exist %s", n.NeUid) + } + + nbState := neDataModel.NBState{ + NeType: neInfo.NeType, + NeId: neInfo.NeId, + RmUid: neInfo.RmUID, + Address: n.Address, + Name: n.Name, + Position: n.Position, + NbName: n.DeviceName, + State: n.State, + Time: date.ParseDateToStr(n.StateTime, time.RFC3339), + } + insertId := s.nbStateService.Insert(nbState) + if insertId <= 0 { + return fmt.Errorf("add nb_state data fail") + } + nbState.ID = insertId + + // 推送到ws订阅组 + switch neInfo.NeType { + case "AMF": + s.wsService.ByGroupID(wsService.GROUP_AMF_NB, nbState) + s.wsService.ByGroupID(fmt.Sprintf("%s_%s", wsService.GROUP_AMF_NB, neInfo.NeId), nbState) + case "MME": + s.wsService.ByGroupID(wsService.GROUP_MME_NB, nbState) + s.wsService.ByGroupID(fmt.Sprintf("%s_%s", wsService.GROUP_MME_NB, neInfo.NeId), nbState) + } + return nil +} diff --git a/src/modules/oam/service/ue_nb.go b/src/modules/oam/service/ue_nb.go new file mode 100644 index 00000000..b8d72107 --- /dev/null +++ b/src/modules/oam/service/ue_nb.go @@ -0,0 +1,69 @@ +package service + +import ( + "encoding/json" + "fmt" + + "github.com/tsmask/go-oam" + + neDataModel "be.ems/src/modules/network_data/model" + neDataRepository "be.ems/src/modules/network_data/repository" + neDataService "be.ems/src/modules/network_data/service" + neService "be.ems/src/modules/network_element/service" + wsService "be.ems/src/modules/ws/service" +) + +// 实例化服务层 UENB 结构体 +var NewUENB = &UENB{ + neInfoService: neService.NewNeInfo, + wsService: wsService.NewWSSend, + ueEventService: neDataService.NewUEEvent, +} + +// UENB 消息处理 +type UENB struct { + neInfoService *neService.NeInfo + wsService *wsService.WSSend + ueEventService *neDataService.UEEvent // UE会话事件服务 +} + +// Resolve 接收处理 +func (s *UENB) Resolve(u oam.UENB) error { + // 是否存在网元 + neInfo := s.neInfoService.FindByRmuid(u.NeUid) + if neInfo.NeType == "" || neInfo.RmUID != u.NeUid { + return fmt.Errorf("resolve ue_nb network element does not exist %s", u.NeUid) + } + // 查询租户ID + tenantID, _ := neDataRepository.NewSysTenant.Query(map[string]string{ + "imsi": u.IMSI, + }) + + uenbByte, _ := json.Marshal(u) + uenbEvent := neDataModel.UEEvent{ + NeType: neInfo.NeType, + NeName: neInfo.NeName, + RmUID: neInfo.RmUID, + Timestamp: u.RecordTime, + EventType: u.Type, + EventJSONStr: string(uenbByte), + TenantID: fmt.Sprintf("%d", tenantID), + } + + insertId := s.ueEventService.Insert(uenbEvent) + if insertId <= 0 { + return fmt.Errorf("add ue_nb data fail") + } + uenbEvent.ID = insertId + + // 推送到ws订阅组 + switch neInfo.NeType { + case "AMF": + s.wsService.ByGroupID(wsService.GROUP_AMF_UE, uenbEvent) + s.wsService.ByGroupID(fmt.Sprintf("%s_%s", wsService.GROUP_AMF_UE, neInfo.NeId), uenbEvent) + case "MME": + s.wsService.ByGroupID(wsService.GROUP_MME_UE, uenbEvent) + s.wsService.ByGroupID(fmt.Sprintf("%s_%s", wsService.GROUP_MME_UE, neInfo.NeId), uenbEvent) + } + return nil +}