Merge branch 'main-v2' into lite

This commit is contained in:
TsMask
2025-08-18 11:10:48 +08:00
358 changed files with 11898 additions and 36289 deletions

View File

@@ -10,8 +10,6 @@ import (
"github.com/spf13/pflag"
"github.com/spf13/viper"
libConfig "be.ems/lib/config"
)
var (
@@ -21,11 +19,10 @@ var (
)
// 程序配置
var conf *viper.Viper
var conf *viper.Viper = viper.New()
// 初始化程序配置
func InitConfig(configDir *embed.FS) {
conf = viper.New()
initFlag()
initViper(configDir)
}
@@ -97,16 +94,14 @@ func initViper(configDir *embed.FS) {
// 外部文件配置
externalConfig := conf.GetString("config")
if externalConfig != "" {
// readExternalConfig(externalConfig)
// 处理旧配置,存在相同的配置项处理
configInMerge(externalConfig)
readExternalConfig(externalConfig)
}
// 记录程序开始运行的时间点
conf.Set("runTime", time.Now())
}
// readExternalConfig 读取外部文件配置(放弃旧的配置序列化时候才用)
// readExternalConfig 读取外部文件配置
func readExternalConfig(configPaht string) {
f, err := os.Open(configPaht)
if err != nil {
@@ -121,38 +116,6 @@ func readExternalConfig(configPaht string) {
}
}
// 配置文件读取进行内部参数合并
func configInMerge(configFile string) {
// 指定配置文件读取序列化
libConfig.ReadConfig(configFile)
uriPrefix := libConfig.GetYamlConfig().OMC.UriPrefix
if uriPrefix != "" {
libConfig.UriPrefix = uriPrefix
}
if libConfig.GetYamlConfig().TestConfig.Enabled {
libConfig.ReadTestConfigYaml(libConfig.GetYamlConfig().TestConfig.File)
}
// 配置文件读取
var v = viper.New()
// 设置配置文件路径
v.SetConfigFile(configFile)
v.SetConfigType("yaml")
// 读取配置文件
if err := v.ReadInConfig(); err != nil {
fmt.Printf("failure to read configuration file: %v \n", err)
return
}
// 合并外层lib和features使用配置
for key, value := range v.AllSettings() {
// 跳过配置
if key == "testconfig" || key == "logger" {
continue
}
conf.Set(key, value)
}
}
// Env 获取运行服务环境
// local prod
func Env() string {

View File

@@ -0,0 +1,30 @@
package config
import (
"fmt"
"os"
"regexp"
)
// SedReplace 替换文件内容文件来自外部文件配置config传入
//
// sed 's/port: [0-9]\+ # trace port/port: 6964 # trace port/' /usr/local/etc/omc/omc.yaml
func SedReplace(pattern, replacement string) error {
// 外部文件配置
externalConfig := conf.GetString("config")
if externalConfig == "" {
return fmt.Errorf("config file path not found")
}
// 读取文件内容
data, err := os.ReadFile(externalConfig)
if err != nil {
return err
}
// 定义正则表达式
re := regexp.MustCompile(pattern)
// 使用正则替换,将匹配到的部分替换为新的内容
replacedData := re.ReplaceAll(data, []byte(replacement))
// 写回文件
return os.WriteFile(externalConfig, replacedData, 0644)
}

View File

@@ -1,13 +1,19 @@
package constants
// 告警 alarmCode 常量
const (
// ALARM_EVENT_REBOOT 事件-网元重启
ALARM_EVENT_REBOOT = 9000
// ALARM_STATE_CHECK 告警-状态检查
ALARM_STATE_CHECK = 10000
// ALARM_RAM_CPU_CHECK 告警-内存/CPU/磁盘检查
ALARM_CMD_CHECK = 10001
// ALARM_LICENSE_CHECK 告警-网元License到期检查
ALARM_LICENSE_CHECK = 10002
ALARM_EVENT_REBOOT = 9000 // 告警Code-事件-网元重启
ALARM_STATE_CHECK = 10000 // 告警Code-状态检查
ALARM_CMD_CHECK = 10001 // 告警Code-内存/CPU/磁盘检查
ALARM_LICENSE_CHECK = 10002 // 告警Code-网元License到期检查
)
const (
ALARM_ACK_STATE_NOT_ACK = "NotAck" // 告警确认状态-未确认
ALARM_ACK_STATE_ACK = "Ack" // 告警确认状态-已确认
)
const (
ALARM_CLEAR_TYPE_NOT_CLEAR = "NotClear" // 告警清除状态-未清除
ALARM_CLEAR_TYPE_AUTO_CLEAR = "AutoClear" // 告警清除状态-自动清除
ALARM_CLEAR_TYPE_MANUAL_CLEAR = "ManualClear" // 告警清除状态-手动清除
)

View File

@@ -103,6 +103,10 @@ func processSQLFile(db *gorm.DB, filePath string) {
} else if strings.Contains(errorStr, "duplicate key") {
// 忽略重复索引错误
// Error 1061 (42000): Duplicate key name 'key_name'
} else if strings.Contains(errorStr, "duplicate entry") {
// 忽略重复记录错误
// Error 1062 (23000): Duplicate entry 'value' for key 'key_name'
log.Println(err.Error())
} else if strings.Contains(errorStr, "unknown column") {
// 忽略未知字段错误
// Error 1054 (42S22): Unknown column 'field_name' in 'table'
@@ -110,6 +114,10 @@ func processSQLFile(db *gorm.DB, filePath string) {
// 忽略删除字段或索引错误
// Error 1091 (42000): Can't DROP COLUMN `field_name`; check that it exists
// Error 1091 (42000): Can't DROP 'idx_ne_type_id'; check that column/key exists
} else if strings.Contains(errorStr, "doesn't match") {
// 忽略列数不匹配错误
// Error 1136 (21S01): Column count doesn't match value count at row 1
log.Println(err.Error())
} else {
// 其他错误终止程序
log.Fatalln(errorStr)

View File

@@ -2,7 +2,6 @@ package middleware
import (
"encoding/json"
"fmt"
"reflect"
"strings"
"time"
@@ -134,8 +133,15 @@ func OperateLog(options Options) gin.HandlerFunc {
contentDisposition := c.Writer.Header().Get("Content-Disposition")
contentType := c.Writer.Header().Get("Content-Type")
content := contentType + contentDisposition
msg := fmt.Sprintf(`{"status":"%d","size":%d,"content-type":"%s"}`, status, c.Writer.Size(), content)
operaLog.OperaMsg = msg
msgByte, err := json.Marshal(map[string]any{
"status": status,
"size": c.Writer.Size(),
"content-type": content,
})
if err != nil {
operaLog.OperaMsg = ""
}
operaLog.OperaMsg = string(msgByte)
}
// 日志记录时间

View File

@@ -1,96 +0,0 @@
package socket
import (
"bytes"
"fmt"
"net"
"strings"
"time"
)
// ConnTCP 连接TCP客户端
type ConnTCP struct {
Addr string `json:"addr"` // 主机地址
Port int64 `json:"port"` // 端口
DialTimeOut time.Duration `json:"dialTimeOut"` // 连接超时断开
Client *net.Conn `json:"client"`
LastResult string `json:"lastResult"` // 记最后一次发送消息的结果
}
// New 创建TCP客户端
func (c *ConnTCP) New() (*ConnTCP, error) {
// IPV6地址协议
proto := "tcp"
if strings.Contains(c.Addr, ":") {
proto = "tcp6"
c.Addr = fmt.Sprintf("[%s]", c.Addr)
}
address := net.JoinHostPort(c.Addr, fmt.Sprint(c.Port))
// 默认等待5s
if c.DialTimeOut == 0 {
c.DialTimeOut = 5 * time.Second
}
// 连接到服务端
client, err := net.DialTimeout(proto, address, c.DialTimeOut)
if err != nil {
return nil, err
}
c.Client = &client
return c, nil
}
// Close 关闭当前TCP客户端
func (c *ConnTCP) Close() {
if c.Client != nil {
(*c.Client).Close()
}
}
// Send 发送消息
func (c *ConnTCP) Send(msg []byte, timer time.Duration) (string, error) {
if c.Client == nil {
return "", fmt.Errorf("tcp client not connected")
}
conn := *c.Client
// 写入信息
if len(msg) > 0 {
if _, err := conn.Write(msg); err != nil {
return "", err
}
}
var buf bytes.Buffer
defer buf.Reset()
tmp := make([]byte, 1024)
for {
select {
case <-time.After(timer):
c.LastResult = buf.String()
return c.LastResult, fmt.Errorf("timeout")
default:
// 读取命令消息
n, err := conn.Read(tmp)
if n == 0 || err != nil {
tmp = nil
break
}
tmpStr := string(tmp[:n])
buf.WriteString(tmpStr)
// 是否有终止符
if strings.HasSuffix(tmpStr, ">") || strings.HasSuffix(tmpStr, "> ") || strings.HasSuffix(tmpStr, "# ") {
tmp = nil
c.LastResult = buf.String()
return c.LastResult, nil
}
}
}
}

View File

@@ -1,83 +0,0 @@
package socket
import (
"fmt"
"net"
"strings"
"be.ems/src/framework/logger"
)
// SocketTCP TCP服务端
type SocketTCP struct {
Addr string `json:"addr"` // 主机地址
Port int64 `json:"port"` // 端口
Listener *net.TCPListener `json:"listener"`
StopChan chan struct{} `json:"stop"` // 停止信号
}
// New 创建TCP服务端
func (s *SocketTCP) New() (*SocketTCP, error) {
// IPV6地址协议
proto := "tcp"
if strings.Contains(s.Addr, ":") {
proto = "tcp6"
s.Addr = fmt.Sprintf("[%s]", s.Addr)
}
address := fmt.Sprintf("%s:%d", s.Addr, s.Port)
// 解析 TCP 地址
tcpAddr, err := net.ResolveTCPAddr(proto, address)
if err != nil {
return nil, err
}
// 监听 TCP 地址
listener, err := net.ListenTCP(proto, tcpAddr)
if err != nil {
return nil, err
}
s.Listener = listener
s.StopChan = make(chan struct{}, 1)
return s, nil
}
// Close 关闭当前TCP服务端
func (s *SocketTCP) Close() {
if s.Listener != nil {
s.StopChan <- struct{}{}
(*s.Listener).Close()
}
}
// Resolve 处理消息
func (s *SocketTCP) Resolve(callback func(conn *net.Conn, err error)) {
if s.Listener == nil {
callback(nil, fmt.Errorf("tcp service not created"))
return
}
defer func() {
if err := recover(); err != nil {
callback(nil, fmt.Errorf("tcp service panic err"))
}
}()
listener := *s.Listener
for {
select {
case <-s.StopChan:
callback(nil, fmt.Errorf("udp service stop"))
return
default:
conn, err := listener.Accept()
if err != nil {
logger.Errorf("Error accepting connection: %v ", err)
continue
}
defer conn.Close()
callback(&conn, nil)
}
}
}

View File

@@ -1,96 +0,0 @@
package socket
import (
"bytes"
"fmt"
"net"
"strings"
"time"
)
// ConnUDP 连接UDP客户端
type ConnUDP struct {
Addr string `json:"addr"` // 主机地址
Port int64 `json:"port"` // 端口
DialTimeOut time.Duration `json:"dialTimeOut"` // 连接超时断开
Client *net.Conn `json:"client"`
LastResult string `json:"lastResult"` // 记最后一次发送消息的结果
}
// New 创建UDP客户端
func (c *ConnUDP) New() (*ConnUDP, error) {
// IPV6地址协议
proto := "udp"
if strings.Contains(c.Addr, ":") {
proto = "udp6"
c.Addr = fmt.Sprintf("[%s]", c.Addr)
}
address := net.JoinHostPort(c.Addr, fmt.Sprint(c.Port))
// 默认等待5s
if c.DialTimeOut == 0 {
c.DialTimeOut = 5 * time.Second
}
// 连接到服务端
client, err := net.DialTimeout(proto, address, c.DialTimeOut)
if err != nil {
return nil, err
}
c.Client = &client
return c, nil
}
// Close 关闭当前UDP客户端
func (c *ConnUDP) Close() {
if c.Client != nil {
(*c.Client).Close()
}
}
// Send 发送消息
func (c *ConnUDP) Send(msg []byte, ms int) (string, error) {
if c.Client == nil {
return "", fmt.Errorf("udp client not connected")
}
conn := *c.Client
// 写入信息
if len(msg) > 0 {
if _, err := conn.Write(msg); err != nil {
return "", err
}
}
var buf bytes.Buffer
defer buf.Reset()
tmp := make([]byte, 1024)
for {
select {
case <-time.After(time.Duration(time.Duration(ms).Milliseconds())):
c.LastResult = buf.String()
return c.LastResult, fmt.Errorf("timeout")
default:
// 读取命令消息
n, err := conn.Read(tmp)
if n == 0 || err != nil {
tmp = nil
break
}
tmpStr := string(tmp[:n])
buf.WriteString(tmpStr)
// 是否有终止符
if strings.HasSuffix(tmpStr, ">") || strings.HasSuffix(tmpStr, "> ") || strings.HasSuffix(tmpStr, "# ") {
tmp = nil
c.LastResult = buf.String()
return c.LastResult, nil
}
}
}
}

View File

@@ -1,74 +0,0 @@
package socket
import (
"fmt"
"net"
"strings"
)
// SocketUDP UDP服务端
type SocketUDP struct {
Addr string `json:"addr"` // 主机地址
Port int64 `json:"port"` // 端口
Conn *net.UDPConn `json:"conn"`
StopChan chan struct{} `json:"stop"` // 停止信号
}
// New 创建UDP服务端
func (s *SocketUDP) New() (*SocketUDP, error) {
// IPV6地址协议
proto := "udp"
if strings.Contains(s.Addr, ":") {
proto = "udp6"
s.Addr = fmt.Sprintf("[%s]", s.Addr)
}
address := fmt.Sprintf("%s:%d", s.Addr, s.Port)
// 解析 UDP 地址
udpAddr, err := net.ResolveUDPAddr(proto, address)
if err != nil {
return nil, err
}
// 监听 UDP 地址
conn, err := net.ListenUDP("udp", udpAddr)
if err != nil {
return nil, err
}
s.Conn = conn
s.StopChan = make(chan struct{}, 1)
return s, nil
}
// CloseService 关闭当前UDP服务端
func (s *SocketUDP) Close() {
if s.Conn != nil {
s.StopChan <- struct{}{}
(*s.Conn).Close()
}
}
// Resolve 处理消息
func (s *SocketUDP) Resolve(callback func(*net.UDPConn, error)) {
if s.Conn == nil {
callback(nil, fmt.Errorf("udp service not created"))
return
}
defer func() {
if err := recover(); err != nil {
callback(nil, fmt.Errorf("udp service panic err"))
}
}()
for {
select {
case <-s.StopChan:
callback(nil, fmt.Errorf("udp service not created"))
return
default:
callback(s.Conn, nil)
}
}
}

View File

@@ -42,10 +42,14 @@ func (c *ConnTelnet) NewClient() (*ConnTelnet, error) {
}
// 进行登录
time.Sleep(100 * time.Millisecond)
client.Write([]byte(c.User + "\r\n"))
time.Sleep(100 * time.Millisecond)
client.Write([]byte(c.Password + "\r\n"))
if c.User != "" {
time.Sleep(100 * time.Millisecond)
client.Write([]byte(c.User + "\r\n"))
}
if c.Password != "" {
time.Sleep(100 * time.Millisecond)
client.Write([]byte(c.Password + "\r\n"))
}
// fmt.Fprintln(client, c.User)
// fmt.Fprintln(client, c.Password)
@@ -103,6 +107,19 @@ func (c *ConnTelnet) RunCMD(cmd string) (string, error) {
return c.LastResult, nil
}
// WindowChange informs the remote host about a terminal window dimension change to h rows and w columns.
func (s *ConnTelnet) WindowChange(h, w int) error {
if s.Client == nil {
return fmt.Errorf("client is nil to content write failed")
}
conn := *s.Client
// 需要确保接收方理解并正确处理发送窗口大小设置命令
conn.Write([]byte{255, 251, 31})
conn.Write([]byte{255, 250, 31, byte(w >> 8), byte(w & 0xFF), byte(h >> 8), byte(h & 0xFF), 255, 240})
return nil
}
// NewClient 创建Telnet客户端会话对象
func (c *ConnTelnet) NewClientSession(cols, rows int) (*TelnetClientSession, error) {
if c.Client == nil {

View File

@@ -54,9 +54,9 @@ func ParseDateToStr(date any, formatStr string) string {
if v == 0 {
return ""
}
if v > 9999999999 {
if v > 1e12 {
t = time.UnixMilli(v)
} else if v > 999999999 {
} else if v > 1e9 {
t = time.Unix(v, 0)
} else {
logger.Infof("utils ParseDateToStr err %v", "Invalid timestamp")

View File

@@ -0,0 +1,44 @@
package expr
import (
"fmt"
"regexp"
"strings"
"github.com/expr-lang/expr"
)
// Eval 计算表达式返回结果
func Eval(exprStr string, env map[string]any) (any, error) {
return expr.Eval(exprStr, env)
}
// ParseExprEnv 解析表达式环境变量
// 比如 "('SMF.03'/'SMF.04')*100"
// 变量传入"SMF.03": 3
func ParseExprEnv(exprStr string, env map[string]any) (string, map[string]any) {
// 使用正则表达式匹配带单引号的变量名
re := regexp.MustCompile(`'([^']+)'`)
tempEnv := make(map[string]any)
tempExpr := exprStr
varCount := 0
matches := re.FindAllStringSubmatch(exprStr, -1)
for _, match := range matches {
paramName := match[1]
tempVarName := fmt.Sprintf("var%d", varCount)
tempEnv[tempVarName] = env[paramName]
tempExpr = strings.Replace(tempExpr, match[0], tempVarName, 1)
varCount++
}
// 合并临时环境变量和原环境变量
combinedEnv := make(map[string]any)
for k, v := range env {
combinedEnv[k] = v
}
for k, v := range tempEnv {
combinedEnv[k] = v
}
return tempExpr, combinedEnv
}

View File

@@ -19,7 +19,7 @@ func ValidUsername(username string) bool {
if username == "" {
return false
}
pattern := `^[a-zA-Z][a-z0-9A-Z]{5,}`
pattern := `^.{4,}$` //`^[a-zA-Z][a-z0-9A-Z]{5,}`
match, err := regexp.MatchString(pattern, username)
if err != nil {
return false