merge: 合并OMC分支
This commit is contained in:
@@ -27,9 +27,9 @@ func InitConfig() {
|
||||
func initFlag() {
|
||||
// --env prod
|
||||
pflag.String("env", "prod", "Specify Run Environment Configuration local or prod")
|
||||
// --c /etc/omc.yaml
|
||||
// -c /etc/omc.yaml
|
||||
pConfig := pflag.StringP("config", "c", "./etc/omc.yaml", "Specify Configuration File")
|
||||
// --c /etc/restconf.yaml
|
||||
// -c /etc/restconf.yaml
|
||||
pConfig := pflag.StringP("config", "c", "./etc/restconf.yaml", "Specify Configuration File")
|
||||
// --version
|
||||
// -V
|
||||
pVersion := pflag.BoolP("version", "V", false, "Output program version")
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# 项目信息
|
||||
framework:
|
||||
name: "CN EMS"
|
||||
version: "2.2403.3"
|
||||
version: "2.2407.1"
|
||||
|
||||
# 应用服务配置
|
||||
server:
|
||||
@@ -15,7 +15,7 @@ logger:
|
||||
fileDir: "/var/log"
|
||||
fileName: "omc.log"
|
||||
level: 2 # 日志记录的等级 0:silent<1:info<2:warn<3:error
|
||||
maxDay: 180 # 日志会保留 180 天
|
||||
maxDay: 7 # 日志会保留 180 天
|
||||
maxSize: 10 # 调整按 10MB 大小的切割
|
||||
|
||||
# 静态文件配置, 相对项目根路径或填绝对路径
|
||||
@@ -68,6 +68,8 @@ upload:
|
||||
# 软件包
|
||||
- ".deb"
|
||||
- ".rpm"
|
||||
# 验证文件
|
||||
- ".ini"
|
||||
|
||||
# cors 跨域
|
||||
cors:
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package admin
|
||||
|
||||
// 管理员常量信息
|
||||
// 系统管理员常量信息
|
||||
|
||||
// 管理员-系统指定角色ID
|
||||
// 系统管理员-系统指定角色ID
|
||||
const ROLE_ID = "1"
|
||||
|
||||
// 管理员-系统指定角色KEY
|
||||
const ROLE_KEY = "admin"
|
||||
// 系统管理员-系统指定角色KEY
|
||||
const ROLE_KEY = "system"
|
||||
|
||||
// 管理员-系统指定权限
|
||||
// 系统管理员-系统指定权限
|
||||
const PERMISSION = "*:*:*"
|
||||
|
||||
@@ -19,3 +19,6 @@ const STATUS_NO = "0"
|
||||
|
||||
// 上下文信息-登录用户
|
||||
const CTX_LOGIN_USER = "loginuser"
|
||||
|
||||
// 启动-引导系统初始
|
||||
const LAUNCH_BOOTLOADER = "bootloader"
|
||||
|
||||
@@ -26,6 +26,9 @@ const (
|
||||
|
||||
// 软件包
|
||||
SOFTWARE = "software"
|
||||
|
||||
// 授权文件
|
||||
LICENSE = "license"
|
||||
)
|
||||
|
||||
// 子路径类型映射
|
||||
@@ -38,4 +41,5 @@ var UploadSubpath = map[string]string{
|
||||
DOWNLOAD: "下载",
|
||||
CHUNK: "切片",
|
||||
SOFTWARE: "软件包",
|
||||
LICENSE: "授权文件",
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ const (
|
||||
// NewLogger 实例日志器对象
|
||||
func NewLogger(env, fileDir, fileName string, level, maxDay, maxSize int) (*Logger, error) {
|
||||
logFilePath := filepath.Join(fileDir, fileName)
|
||||
if err := os.MkdirAll(filepath.Dir(logFilePath), 0750); err != nil {
|
||||
if err := os.MkdirAll(filepath.Dir(logFilePath), 0775); err != nil {
|
||||
return nil, fmt.Errorf("failed to mkdir logger dir: %v", err)
|
||||
}
|
||||
fileHandle, err := os.OpenFile(logFilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"time"
|
||||
|
||||
"nms_cxy/src/framework/constants/common"
|
||||
tokenConstants "nms_cxy/src/framework/constants/token"
|
||||
"nms_cxy/src/framework/i18n"
|
||||
"nms_cxy/src/framework/utils/ctx"
|
||||
"nms_cxy/src/framework/utils/parse"
|
||||
@@ -178,6 +179,8 @@ var maskProperties []string = []string{
|
||||
"oldPassword",
|
||||
"newPassword",
|
||||
"confirmPassword",
|
||||
tokenConstants.RESPONSE_FIELD,
|
||||
tokenConstants.ACCESS_TOKEN,
|
||||
}
|
||||
|
||||
// processSensitiveFields 处理敏感属性字段
|
||||
|
||||
@@ -20,6 +20,7 @@ var URL_WHITE_LIST = []string{
|
||||
"/systemState",
|
||||
"/omcNeConfig",
|
||||
"/cdrEvent",
|
||||
"/ueEvent",
|
||||
"/upload-ue",
|
||||
"/oauth/token",
|
||||
}
|
||||
|
||||
@@ -10,21 +10,17 @@ import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// aesKey 字符串AES加解密密钥
|
||||
const aesKey = "AGT66VfY4SMaiT97"
|
||||
|
||||
// StringEncryptByAES 字符串AES加密
|
||||
func StringEncryptByAES(text string) (string, error) {
|
||||
if len(text) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
pass := []byte(text)
|
||||
xpass, err := aesEncryptWithSalt([]byte(aesKey), pass)
|
||||
if err == nil {
|
||||
pass64 := base64.StdEncoding.EncodeToString(xpass)
|
||||
return pass64, err
|
||||
xpass, err := aesEncryptWithSalt([]byte(text))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return "", err
|
||||
pass64 := base64.StdEncoding.EncodeToString(xpass)
|
||||
return pass64, nil
|
||||
}
|
||||
|
||||
// StringDecryptByAES 字符串AES解密
|
||||
@@ -36,53 +32,70 @@ func StringDecryptByAES(text string) (string, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var tpass []byte
|
||||
tpass, err = aesDecryptWithSalt([]byte(aesKey), bytesPass)
|
||||
if err == nil {
|
||||
result := string(tpass[:])
|
||||
return result, err
|
||||
|
||||
tpass, err := aesDecryptWithSalt(bytesPass)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return "", err
|
||||
return string(tpass), nil
|
||||
}
|
||||
|
||||
// aesEncryptWithSalt AES加密
|
||||
func aesEncryptWithSalt(key, plaintext []byte) ([]byte, error) {
|
||||
blockSize := aes.BlockSize
|
||||
padding := blockSize - len(plaintext)%blockSize
|
||||
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
|
||||
plaintext = append(plaintext, padtext...)
|
||||
// aesKey 字符串AES加解密密钥
|
||||
const aesKey = "AGT66VfY4SMaiT97a7df0aef1704d5c5"
|
||||
|
||||
block, err := aes.NewCipher(key)
|
||||
// const aesKey = "AGT66VfY4SMaiT97"
|
||||
// aesEncryptWithSalt AES加密
|
||||
func aesEncryptWithSalt(plaintext []byte) ([]byte, error) {
|
||||
block, err := aes.NewCipher([]byte(aesKey))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
blockSize := aes.BlockSize
|
||||
|
||||
padding := blockSize - (len(plaintext) % blockSize)
|
||||
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
|
||||
plaintext = append(plaintext, padtext...)
|
||||
|
||||
ciphertext := make([]byte, blockSize+len(plaintext))
|
||||
iv := ciphertext[0:blockSize]
|
||||
iv := ciphertext[:blockSize]
|
||||
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cbc := cipher.NewCBCEncrypter(block, iv)
|
||||
cbc.CryptBlocks(ciphertext[blockSize:], plaintext)
|
||||
|
||||
mode := cipher.NewCBCEncrypter(block, iv)
|
||||
mode.CryptBlocks(ciphertext[blockSize:], plaintext)
|
||||
|
||||
return ciphertext, nil
|
||||
}
|
||||
|
||||
// aesDecryptWithSalt AES解密
|
||||
func aesDecryptWithSalt(key, ciphertext []byte) ([]byte, error) {
|
||||
func aesDecryptWithSalt(ciphertext []byte) ([]byte, error) {
|
||||
blockSize := aes.BlockSize
|
||||
var block cipher.Block
|
||||
block, err := aes.NewCipher(key)
|
||||
if len(ciphertext) < blockSize {
|
||||
return nil, fmt.Errorf("ciphertext too short")
|
||||
}
|
||||
|
||||
iv := ciphertext[:blockSize]
|
||||
ciphertext = ciphertext[blockSize:]
|
||||
|
||||
block, err := aes.NewCipher([]byte(aesKey))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(ciphertext) < blockSize {
|
||||
return nil, fmt.Errorf("iciphertext too short")
|
||||
|
||||
if len(ciphertext)%blockSize != 0 {
|
||||
return nil, fmt.Errorf("ciphertext is not a multiple of the block size")
|
||||
}
|
||||
iv := ciphertext[:blockSize]
|
||||
ciphertext = ciphertext[blockSize:]
|
||||
cbc := cipher.NewCBCDecrypter(block, iv)
|
||||
cbc.CryptBlocks(ciphertext, ciphertext)
|
||||
length := len(ciphertext)
|
||||
unpadding := int(ciphertext[len(ciphertext)-1])
|
||||
ciphertext = ciphertext[:(length - unpadding)]
|
||||
|
||||
mode := cipher.NewCBCDecrypter(block, iv)
|
||||
mode.CryptBlocks(ciphertext, ciphertext)
|
||||
|
||||
// 去除填充
|
||||
padding := int(ciphertext[len(ciphertext)-1])
|
||||
if padding > blockSize || padding == 0 {
|
||||
return nil, fmt.Errorf("invalid padding")
|
||||
}
|
||||
ciphertext = ciphertext[:len(ciphertext)-padding]
|
||||
|
||||
return ciphertext, nil
|
||||
}
|
||||
|
||||
@@ -85,8 +85,8 @@ func Authorization(c *gin.Context) string {
|
||||
return ""
|
||||
}
|
||||
// 拆分 Authorization 请求头,提取 JWT 令牌部分
|
||||
arr := strings.Split(authHeader, token.HEADER_PREFIX)
|
||||
if len(arr) == 2 && arr[1] == "" {
|
||||
arr := strings.SplitN(authHeader, token.HEADER_PREFIX, 2)
|
||||
if len(arr) < 2 {
|
||||
return ""
|
||||
}
|
||||
return arr[1]
|
||||
@@ -213,6 +213,11 @@ func LoginUserToDataScopeSQL(c *gin.Context, deptAlias string, userAlias string)
|
||||
conditions = append(conditions, sql)
|
||||
}
|
||||
|
||||
if roledatascope.DEPT == dataScope {
|
||||
sql := fmt.Sprintf(`%s.dept_id = '%s'`, deptAlias, userInfo.DeptID)
|
||||
conditions = append(conditions, sql)
|
||||
}
|
||||
|
||||
if roledatascope.DEPT_AND_CHILD == dataScope {
|
||||
sql := fmt.Sprintf(`%s.dept_id IN ( SELECT dept_id FROM sys_dept WHERE dept_id = '%s' or find_in_set('%s' , ancestors ) )`, deptAlias, userInfo.DeptID, userInfo.DeptID)
|
||||
conditions = append(conditions, sql)
|
||||
@@ -221,7 +226,7 @@ func LoginUserToDataScopeSQL(c *gin.Context, deptAlias string, userAlias string)
|
||||
if roledatascope.SELF == dataScope {
|
||||
// 数据权限为仅本人且没有userAlias别名不查询任何数据
|
||||
if userAlias == "" {
|
||||
sql := fmt.Sprintf(`%s.dept_id = '0'`, deptAlias)
|
||||
sql := fmt.Sprintf(`%s.parent_id = '0'`, deptAlias)
|
||||
conditions = append(conditions, sql)
|
||||
} else {
|
||||
sql := fmt.Sprintf(`%s.user_id = '%s'`, userAlias, userInfo.UserID)
|
||||
|
||||
@@ -17,6 +17,8 @@ const (
|
||||
YYYYMMDDHHMMSS = "20060102150405"
|
||||
// 年-月-日 时:分:秒 列如:2022-12-30 01:01:59
|
||||
YYYY_MM_DD_HH_MM_SS = "2006-01-02 15:04:05"
|
||||
// 年-月-日T时:分:秒Z时区 列如:2022-12-30T01:01:59+08:00
|
||||
YYYY_MM_DDTHH_MM_SSZ = time.RFC3339
|
||||
)
|
||||
|
||||
// 格式时间字符串
|
||||
@@ -46,7 +48,14 @@ func ParseDateToStr(date any, formatStr string) string {
|
||||
if v == 0 {
|
||||
return ""
|
||||
}
|
||||
t = time.UnixMilli(v)
|
||||
if v > 9999999999 {
|
||||
t = time.UnixMilli(v)
|
||||
} else if v > 999999999 {
|
||||
t = time.Unix(v, 0)
|
||||
} else {
|
||||
logger.Infof("utils ParseDateToStr err %v", "Invalid timestamp")
|
||||
return ""
|
||||
}
|
||||
case string:
|
||||
parsedTime, err := time.Parse(formatStr, v)
|
||||
if err != nil {
|
||||
|
||||
@@ -20,7 +20,7 @@ func WriterFileCSV(data [][]string, filePath string) error {
|
||||
dirPath := filepath.Dir(filePath)
|
||||
|
||||
// 确保文件夹路径存在
|
||||
err := os.MkdirAll(dirPath, os.ModePerm)
|
||||
err := os.MkdirAll(dirPath, 0775)
|
||||
if err != nil {
|
||||
logger.Errorf("MkdirAll dir %v", err)
|
||||
}
|
||||
|
||||
@@ -142,7 +142,7 @@ func WriteSheet(headerCells map[string]string, dataCells []map[string]any, fileN
|
||||
saveFilePath := filepath.Join(dir, filePath, fileName)
|
||||
|
||||
// 创建文件目录
|
||||
if err := os.MkdirAll(filepath.Dir(saveFilePath), 0750); err != nil {
|
||||
if err := os.MkdirAll(filepath.Dir(saveFilePath), 0775); err != nil {
|
||||
return "", fmt.Errorf("failed to create save file %v", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -60,9 +60,12 @@ func uploadWhiteList() []string {
|
||||
// fileName 原始文件名称含后缀,如:logo.png
|
||||
func generateFileName(fileName string) string {
|
||||
fileExt := filepath.Ext(fileName)
|
||||
// 替换掉后缀和特殊字符保留文件名
|
||||
// 去除后缀
|
||||
newFileName := regular.Replace(fileName, fileExt, "")
|
||||
newFileName = regular.Replace(newFileName, `[<>:"\\|?*]+`, "")
|
||||
// 去除非法字符
|
||||
newFileName = regular.Replace(newFileName, `[\\/:*?"<>|]`, "")
|
||||
// 去除空格
|
||||
newFileName = regular.Replace(newFileName, `\s`, "_")
|
||||
newFileName = strings.TrimSpace(newFileName)
|
||||
return fmt.Sprintf("%s_%s%s", newFileName, generate.Code(6), fileExt)
|
||||
}
|
||||
@@ -174,13 +177,13 @@ func ReadUploadFileStream(filePath, headerRange string) (map[string]any, error)
|
||||
"range": "",
|
||||
"chunkSize": 0,
|
||||
"fileSize": 0,
|
||||
"data": nil,
|
||||
"data": []byte{},
|
||||
}
|
||||
|
||||
// 文件大小
|
||||
fileSize := getFileSize(fileAsbPath)
|
||||
if fileSize <= 0 {
|
||||
return result, nil
|
||||
return result, fmt.Errorf("file does not exist")
|
||||
}
|
||||
result["fileSize"] = fileSize
|
||||
|
||||
@@ -309,12 +312,12 @@ func CopyUploadFile(filePath, dst string) error {
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
if err := os.MkdirAll(filepath.Dir(dst), 0750); err != nil {
|
||||
if err := os.MkdirAll(filepath.Dir(dst), 0775); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 如果目标文件已经存在,先将目标文件重命名
|
||||
if _, err := os.Stat(dst); err == nil {
|
||||
if info, err := os.Stat(dst); err == nil && !info.IsDir() {
|
||||
ext := filepath.Ext(dst)
|
||||
name := dst[0 : len(dst)-len(ext)]
|
||||
newName := fmt.Sprintf("%s-%s%s", name, time.Now().Format("20060102_150405"), ext)
|
||||
|
||||
@@ -16,7 +16,7 @@ func WriterFileJSON(data any, filePath string) error {
|
||||
dirPath := filepath.Dir(filePath)
|
||||
|
||||
// 确保文件夹路径存在
|
||||
err := os.MkdirAll(dirPath, os.ModePerm)
|
||||
err := os.MkdirAll(dirPath, 0775)
|
||||
if err != nil {
|
||||
logger.Errorf("CreateFile MkdirAll %v", err)
|
||||
}
|
||||
@@ -46,7 +46,7 @@ func WriterFileJSONLine(data []any, filePath string) error {
|
||||
dirPath := filepath.Dir(filePath)
|
||||
|
||||
// 确保文件夹路径存在
|
||||
err := os.MkdirAll(dirPath, os.ModePerm)
|
||||
err := os.MkdirAll(dirPath, 0775)
|
||||
if err != nil {
|
||||
logger.Errorf("CreateFile MkdirAll %v", err)
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ func WriterFileTXT(data [][]string, sep string, filePath string) error {
|
||||
dirPath := filepath.Dir(filePath)
|
||||
|
||||
// 确保文件夹路径存在
|
||||
err := os.MkdirAll(dirPath, os.ModePerm)
|
||||
err := os.MkdirAll(dirPath, 0775)
|
||||
if err != nil {
|
||||
logger.Errorf("CreateFile MkdirAll %v", err)
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ func transferToNewFile(file *multipart.FileHeader, dst string) error {
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
if err = os.MkdirAll(filepath.Dir(dst), 0750); err != nil {
|
||||
if err = os.MkdirAll(filepath.Dir(dst), 0775); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ func mergeToNewFile(dirPath string, writePath string, fileName string) error {
|
||||
|
||||
// 写入到新路径文件
|
||||
newFilePath := filepath.Join(writePath, fileName)
|
||||
if err = os.MkdirAll(filepath.Dir(newFilePath), 0750); err != nil {
|
||||
if err = os.MkdirAll(filepath.Dir(newFilePath), 0775); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
178
src/framework/utils/machine/launch.go
Normal file
178
src/framework/utils/machine/launch.go
Normal file
@@ -0,0 +1,178 @@
|
||||
package machine
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"os"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"nms_cxy/src/framework/constants/common"
|
||||
"nms_cxy/src/framework/logger"
|
||||
"nms_cxy/src/framework/utils/cmd"
|
||||
"nms_cxy/src/framework/utils/crypto"
|
||||
"nms_cxy/src/framework/utils/parse"
|
||||
)
|
||||
|
||||
// 机器的唯一标识符
|
||||
var Code string
|
||||
|
||||
// 初始信息
|
||||
var LaunchInfo map[string]any
|
||||
|
||||
// codeGenerate 生成机器的唯一标识符
|
||||
func codeGenerate() string {
|
||||
var machineID string
|
||||
|
||||
// 获取主机名
|
||||
hostname, err := os.Hostname()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
machineID += hostname
|
||||
|
||||
// 获取 CPU 信息
|
||||
numCPU := runtime.NumCPU()
|
||||
machineID += fmt.Sprintf("%d", numCPU)
|
||||
|
||||
// 获取操作系统信息
|
||||
osInfo := runtime.GOOS
|
||||
machineID += osInfo
|
||||
|
||||
// 使用哈希函数生成机器码
|
||||
h := fnv.New32a()
|
||||
h.Write([]byte(machineID))
|
||||
machineCode := h.Sum32()
|
||||
|
||||
return fmt.Sprintf("%x", machineCode)
|
||||
}
|
||||
|
||||
// 网管本地路径
|
||||
func filePath() string {
|
||||
filePath := "/usr/local/etc/omc/machine.ini"
|
||||
if runtime.GOOS == "windows" {
|
||||
filePath = fmt.Sprintf("C:%s", filePath)
|
||||
}
|
||||
return filePath
|
||||
}
|
||||
|
||||
// codeFileRead 读取机器保留的信息
|
||||
func codeFileRead() (map[string]any, error) {
|
||||
var mapData map[string]any
|
||||
// 读取文件内容
|
||||
bytes, err := os.ReadFile(filePath())
|
||||
if err != nil {
|
||||
logger.Warnf("CodeFileRead ReadFile => %s", err.Error())
|
||||
return mapData, fmt.Errorf("not file")
|
||||
}
|
||||
content := string(bytes)
|
||||
// 解密
|
||||
contentDe, err := crypto.StringDecryptByAES(content)
|
||||
if err != nil {
|
||||
logger.Errorf("CodeFileRead decrypt: %v", err.Error())
|
||||
return mapData, fmt.Errorf("decrypt fail")
|
||||
}
|
||||
// 序列化Map
|
||||
mapData, err = parse.ConvertConfigToMap("json", string(contentDe))
|
||||
if err != nil {
|
||||
logger.Warnf("CodeFileRead ConvertConfigToMap => %s", err.Error())
|
||||
return mapData, fmt.Errorf("content error")
|
||||
}
|
||||
return mapData, nil
|
||||
}
|
||||
|
||||
// codeFileWrite 写入机器保留的信息
|
||||
func codeFileWrite(data map[string]any) error {
|
||||
jsonByte, _ := json.Marshal(data)
|
||||
// 加密
|
||||
contentEn, err := crypto.StringEncryptByAES(string(jsonByte))
|
||||
if err != nil {
|
||||
logger.Errorf("insert encrypt: %v", err.Error())
|
||||
return fmt.Errorf("encrypt fail")
|
||||
}
|
||||
return parse.ConvertConfigToFile("txt", filePath(), contentEn)
|
||||
}
|
||||
|
||||
// Launch 记录首次安装启动初始信息
|
||||
func Launch() {
|
||||
Code = codeGenerate()
|
||||
// 检查文件是否存在
|
||||
if _, err := os.Stat(filePath()); err != nil {
|
||||
LaunchInfo = map[string]any{
|
||||
"code": Code, // 机器码
|
||||
"useTime": time.Now().UnixMilli(), // 首次使用时间
|
||||
|
||||
common.LAUNCH_BOOTLOADER: true, // 启动引导
|
||||
common.LAUNCH_BOOTLOADER + "Time": 0, // 引导完成时间
|
||||
}
|
||||
codeFileWrite(LaunchInfo)
|
||||
} else {
|
||||
// 读取记录文件
|
||||
data, err := codeFileRead()
|
||||
if err != nil {
|
||||
// 文件异常就重新生成
|
||||
os.Remove(filePath())
|
||||
Launch()
|
||||
return
|
||||
}
|
||||
LaunchInfo = data
|
||||
}
|
||||
}
|
||||
|
||||
// SetLaunchInfo 新增额外的初始信息
|
||||
func SetLaunchInfo(info map[string]any) error {
|
||||
if info == nil {
|
||||
return fmt.Errorf("not info")
|
||||
}
|
||||
|
||||
// 固定值禁止变更
|
||||
constKeys := []string{"code", "useTime"}
|
||||
for k, v := range info {
|
||||
constKey := false
|
||||
for _, ck := range constKeys {
|
||||
if ck == k {
|
||||
constKey = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if constKey {
|
||||
continue
|
||||
} else {
|
||||
LaunchInfo[k] = v
|
||||
}
|
||||
}
|
||||
return codeFileWrite(LaunchInfo)
|
||||
}
|
||||
|
||||
// Bootloader 启动引导标记
|
||||
func Bootloader(flag bool) error {
|
||||
return SetLaunchInfo(map[string]any{
|
||||
common.LAUNCH_BOOTLOADER: flag, // 启动引导 true开 false关
|
||||
common.LAUNCH_BOOTLOADER + "Time": time.Now().UnixMilli(), // 引导完成时间
|
||||
})
|
||||
}
|
||||
|
||||
// Reset 引导数据重置
|
||||
func Reset() error {
|
||||
// 重置数据库
|
||||
if runtime.GOOS == "windows" {
|
||||
// return fmt.Errorf("not support window")
|
||||
} else {
|
||||
// 重置数据库
|
||||
if _, err := cmd.Execf("sudo /usr/local/omc/bin/setomc.sh -m install"); err != nil {
|
||||
return err
|
||||
}
|
||||
// 重启服务
|
||||
if _, err := cmd.Execf("nohup sh -c \"sleep 1s && %s\" > /dev/null 2>&1 &", "sudo systemctl restart restagent"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// 重置引导标记
|
||||
if err := Bootloader(true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,8 +1,11 @@
|
||||
package parse
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"image/color"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
@@ -10,6 +13,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/robfig/cron/v3"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// Number 解析数值型
|
||||
@@ -51,6 +55,8 @@ func Boolean(str any) bool {
|
||||
case float32, float64:
|
||||
num := reflect.ValueOf(str).Float()
|
||||
return num != 0
|
||||
case bool:
|
||||
return str
|
||||
default:
|
||||
return false
|
||||
}
|
||||
@@ -165,3 +171,83 @@ func Color(colorStr string) *color.RGBA {
|
||||
A: 255, // 不透明
|
||||
}
|
||||
}
|
||||
|
||||
// ConvertIPMask 转换IP网络地址掩码 24->"255.255.255.0" 20->"255.255.240.0"
|
||||
func ConvertIPMask(bits int64) string {
|
||||
if bits < 0 || bits > 32 {
|
||||
return "255.255.255.255"
|
||||
}
|
||||
|
||||
// 构建一个32位的uint32类型掩码,指定前bits位为1,其余为0
|
||||
mask := uint32((1<<bits - 1) << (32 - bits))
|
||||
|
||||
// 将掩码转换为四个八位分组
|
||||
groups := []string{
|
||||
fmt.Sprintf("%d", mask>>24),
|
||||
fmt.Sprintf("%d", (mask>>16)&255),
|
||||
fmt.Sprintf("%d", (mask>>8)&255),
|
||||
fmt.Sprintf("%d", mask&255),
|
||||
}
|
||||
|
||||
// 将分组用点号连接起来形成掩码字符串
|
||||
return strings.Join(groups, ".")
|
||||
}
|
||||
|
||||
// ConvertConfigToMap 将配置内容转换为Map结构数据
|
||||
//
|
||||
// configType 类型支持:txt json yaml yml
|
||||
func ConvertConfigToMap(configType, content string) (map[string]any, error) {
|
||||
// 类型支持:viper.SupportedExts
|
||||
// config := viper.New()
|
||||
// config.SetConfigType(configType)
|
||||
// err := config.ReadConfig(bytes.NewBuffer([]byte(content)))
|
||||
// return config.AllSettings(), err
|
||||
|
||||
var configMap map[string]interface{}
|
||||
var err error
|
||||
if configType == "" || configType == "txt" {
|
||||
configMap = map[string]interface{}{
|
||||
"txt": content,
|
||||
}
|
||||
}
|
||||
if configType == "yaml" || configType == "yml" {
|
||||
err = yaml.Unmarshal([]byte(content), &configMap)
|
||||
}
|
||||
if configType == "json" {
|
||||
err = json.Unmarshal([]byte(content), &configMap)
|
||||
}
|
||||
return configMap, err
|
||||
}
|
||||
|
||||
// ConvertConfigToFile 将数据写入到指定文件内
|
||||
//
|
||||
// configType 类型支持:txt json yaml yml
|
||||
func ConvertConfigToFile(configType, filePath string, data any) error {
|
||||
// viper.SupportedExts
|
||||
// config := viper.New()
|
||||
// config.SetConfigType(configType)
|
||||
// for key, value := range mapData {
|
||||
// config.Set(key, value)
|
||||
// }
|
||||
// return config.WriteConfigAs(filePath)
|
||||
|
||||
var dataByte []byte
|
||||
var err error
|
||||
if configType == "" || configType == "txt" {
|
||||
dataByte = []byte(data.(string))
|
||||
}
|
||||
if configType == "yaml" || configType == "yml" {
|
||||
dataByte, err = yaml.Marshal(data)
|
||||
}
|
||||
if configType == "json" {
|
||||
dataByte, err = json.Marshal(data)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(filepath.Dir(filePath), 0775); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(filePath, dataByte, 0644)
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ func ValidMobile(mobile string) bool {
|
||||
if mobile == "" {
|
||||
return false
|
||||
}
|
||||
pattern := `^1[3|4|5|6|7|8|9][0-9]\d{8}$`
|
||||
pattern := `^.{3,}$` // `^1[3|4|5|6|7|8|9][0-9]\d{8}$`
|
||||
match, err := regexp.MatchString(pattern, mobile)
|
||||
if err != nil {
|
||||
return false
|
||||
|
||||
@@ -20,7 +20,7 @@ func PageNumSize(pageNum, pageSize any) (int64, int64) {
|
||||
|
||||
// 显示记录数
|
||||
size := parse.Number(pageSize)
|
||||
if size < 0 {
|
||||
if size < 1 {
|
||||
size = 10
|
||||
}
|
||||
return num - 1, size
|
||||
@@ -66,12 +66,12 @@ func SetFieldValue(obj any, fieldName string, value any) {
|
||||
}
|
||||
fieldValue.SetFloat(floatValue)
|
||||
case reflect.Struct:
|
||||
fmt.Printf("%s 时间解析 %s %v \n", fieldName, fieldValue.Type(), value)
|
||||
fmt.Printf("%s time resolution %s %v \n", fieldName, fieldValue.Type(), value)
|
||||
if fieldValue.Type() == reflect.TypeOf(time.Time{}) && value != nil {
|
||||
// 解析 value 并转换为 time.Time 类型
|
||||
parsedTime, err := time.Parse("2006-01-02 15:04:05 +0800 CST", fmt.Sprintf("%v", value))
|
||||
if err != nil {
|
||||
fmt.Println("时间解析出错:", err)
|
||||
fmt.Println("Time resolution error:", err)
|
||||
} else {
|
||||
// 设置字段的值
|
||||
fieldValue.Set(reflect.ValueOf(parsedTime))
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"nms_cxy/src/framework/config"
|
||||
"nms_cxy/src/framework/logger"
|
||||
"nms_cxy/src/framework/utils/cmd"
|
||||
"nms_cxy/src/framework/utils/parse"
|
||||
@@ -23,35 +22,31 @@ type FileListRow struct {
|
||||
}
|
||||
|
||||
// 文件列表
|
||||
// neIp 网元IP空字符串为本地
|
||||
// search 文件名后模糊*
|
||||
//
|
||||
// return 目录大小,行记录,异常
|
||||
func FileList(path, neIp, search string) (string, []FileListRow, error) {
|
||||
func FileList(sshClient *ConnSSH, path, search string) (string, []FileListRow, error) {
|
||||
totalSize := ""
|
||||
var rows []FileListRow
|
||||
rowStr := ""
|
||||
|
||||
// 发送命令
|
||||
searchStr := ""
|
||||
searchStr := "*"
|
||||
if search != "" {
|
||||
searchStr = search + "*"
|
||||
searchStr = search + searchStr
|
||||
}
|
||||
pathStr := fmt.Sprintf("cd %s \n", path)
|
||||
cmdStr := fmt.Sprintf("ls -lht --time-style=+%%s %s \n", searchStr)
|
||||
cmdStr := fmt.Sprintf("cd %s && ls -lthd --time-style=+%%s %s", path, searchStr)
|
||||
|
||||
// 是否远程读取
|
||||
if neIp != "" {
|
||||
usernameNe := config.Get("ne.user").(string) // 网元统一用户
|
||||
sshHost := fmt.Sprintf("%s@%s", usernameNe, neIp)
|
||||
resultStr, err := cmd.ExecWithCheck("ssh", sshHost, pathStr, cmdStr)
|
||||
// 是否远程客户端读取
|
||||
if sshClient == nil {
|
||||
resultStr, err := cmd.Execf(cmdStr)
|
||||
if err != nil {
|
||||
logger.Errorf("Ne FileList Path: %s, Search: %s, Error:%s", path, search, err.Error())
|
||||
return totalSize, rows, err
|
||||
}
|
||||
rowStr = resultStr
|
||||
} else {
|
||||
resultStr, err := cmd.Execf(pathStr, cmdStr)
|
||||
resultStr, err := sshClient.RunCMD(cmdStr)
|
||||
if err != nil {
|
||||
logger.Errorf("Ne FileList Path: %s, Search: %s, Error:%s", path, search, err.Error())
|
||||
return totalSize, rows, err
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
||||
"nms_cxy/src/framework/config"
|
||||
"nms_cxy/src/framework/logger"
|
||||
)
|
||||
|
||||
// 网元NE 文件复制到远程文件
|
||||
func FileSCPLocalToNe(neIp, localPath, nePath string) error {
|
||||
usernameNe := config.Get("ne.user").(string)
|
||||
// scp /path/to/local/file.txt user@remote-server:/path/to/remote/directory/
|
||||
neDir := fmt.Sprintf("%s@%s:%s", usernameNe, neIp, nePath)
|
||||
cmd := exec.Command("scp", "-r", localPath, neDir)
|
||||
_, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
logger.Errorf("FileSCPLocalToNe %s", err.Error())
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 网元NE 远程文件复制到本地文件
|
||||
func FileSCPNeToLocal(neIp, nePath, localPath string) error {
|
||||
// 确保文件夹路径存在
|
||||
if err := os.MkdirAll(filepath.Dir(localPath), 0750); err != nil {
|
||||
logger.Errorf("FileSCPNeToLocal MkdirAll err %v", err)
|
||||
return err
|
||||
}
|
||||
usernameNe := config.Get("ne.user").(string)
|
||||
// scp user@remote-server:/path/to/remote/directory/ /path/to/local/file.txt
|
||||
neDir := fmt.Sprintf("%s@%s:%s", usernameNe, neIp, nePath)
|
||||
cmd := exec.Command("scp", "-r", neDir, localPath)
|
||||
_, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
logger.Errorf("FileSCPNeToLocal %s", err.Error())
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
203
src/framework/utils/ssh/sftp.go
Normal file
203
src/framework/utils/ssh/sftp.go
Normal file
@@ -0,0 +1,203 @@
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"nms_cxy/src/framework/logger"
|
||||
|
||||
gosftp "github.com/pkg/sftp"
|
||||
)
|
||||
|
||||
// SSHClientSFTP SSH客户端SFTP对象
|
||||
type SSHClientSFTP struct {
|
||||
Client *gosftp.Client
|
||||
}
|
||||
|
||||
// Close 关闭会话
|
||||
func (s *SSHClientSFTP) Close() {
|
||||
if s.Client != nil {
|
||||
s.Client.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// CopyDirRemoteToLocal 复制目录-远程到本地
|
||||
func (s *SSHClientSFTP) CopyDirRemoteToLocal(remoteDir, localDir string) error {
|
||||
// 列出远程目录中的文件和子目录
|
||||
remoteFiles, err := s.Client.ReadDir(remoteDir)
|
||||
if err != nil {
|
||||
logger.Errorf("CopyDirRemoteToLocal failed to reading remote directory %s: => %s", remoteDir, err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
// 创建本地目录
|
||||
err = os.MkdirAll(localDir, 0775)
|
||||
if err != nil {
|
||||
logger.Errorf("CopyDirRemoteToLocal failed to creating local directory %s: => %s", localDir, err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
// 遍历远程文件和子目录并复制到本地
|
||||
for _, remoteFile := range remoteFiles {
|
||||
remotePath := filepath.Join(remoteDir, remoteFile.Name())
|
||||
localPath := filepath.Join(localDir, remoteFile.Name())
|
||||
|
||||
if remoteFile.IsDir() {
|
||||
// 如果是子目录,则递归复制子目录
|
||||
err = s.CopyDirRemoteToLocal(remotePath, localPath)
|
||||
if err != nil {
|
||||
logger.Errorf("CopyDirRemoteToLocal failed to copying remote directory %s: => %s", remotePath, err.Error())
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
// 如果是文件,则复制文件内容
|
||||
remoteFile, err := s.Client.Open(remotePath)
|
||||
if err != nil {
|
||||
logger.Errorf("CopyDirRemoteToLocal failed to opening remote file %s: => %s", remotePath, err.Error())
|
||||
continue
|
||||
}
|
||||
defer remoteFile.Close()
|
||||
|
||||
localFile, err := os.Create(localPath)
|
||||
if err != nil {
|
||||
logger.Errorf("CopyDirRemoteToLocal failed to creating local file %s: => %s", localPath, err.Error())
|
||||
continue
|
||||
}
|
||||
defer localFile.Close()
|
||||
|
||||
_, err = io.Copy(localFile, remoteFile)
|
||||
if err != nil {
|
||||
logger.Errorf("CopyDirRemoteToLocal failed to copying file contents from %s to %s: => %s", remotePath, localPath, err.Error())
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CopyDirRemoteToLocal 复制目录-本地到远程
|
||||
func (s *SSHClientSFTP) CopyDirLocalToRemote(localDir, remoteDir string) error {
|
||||
// 创建远程目录
|
||||
err := s.Client.MkdirAll(remoteDir)
|
||||
if err != nil {
|
||||
logger.Errorf("CopyDirLocalToRemote failed to creating remote directory %s: => %s", remoteDir, err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
// 遍历本地目录中的文件和子目录并复制到远程
|
||||
err = filepath.Walk(localDir, func(localPath string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 生成远程路径
|
||||
remotePath := filepath.Join(remoteDir, localPath[len(localDir):])
|
||||
|
||||
if info.IsDir() {
|
||||
// 如果是子目录,则创建远程目录
|
||||
err := s.Client.MkdirAll(remotePath)
|
||||
if err != nil {
|
||||
logger.Errorf("CopyDirLocalToRemote failed to creating remote directory %s: => %s", remotePath, err.Error())
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
// 如果是文件,则复制文件内容
|
||||
localFile, err := os.Open(localPath)
|
||||
if err != nil {
|
||||
logger.Errorf("CopyDirLocalToRemote failed to opening local file %s: => %s", localPath, err.Error())
|
||||
return nil
|
||||
}
|
||||
defer localFile.Close()
|
||||
|
||||
remoteFile, err := s.Client.Create(remotePath)
|
||||
if err != nil {
|
||||
logger.Errorf("CopyDirLocalToRemote failed to creating remote file %s: => %s", remotePath, err.Error())
|
||||
return nil
|
||||
}
|
||||
defer remoteFile.Close()
|
||||
|
||||
_, err = io.Copy(remoteFile, localFile)
|
||||
if err != nil {
|
||||
logger.Errorf("CopyDirLocalToRemote failed to copying file contents from %s to %s: => %s", localPath, remotePath, err.Error())
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
logger.Errorf("CopyDirLocalToRemote failed to walking local directory: => %s", err.Error())
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CopyDirRemoteToLocal 复制文件-远程到本地
|
||||
func (s *SSHClientSFTP) CopyFileRemoteToLocal(remotePath, localPath string) error {
|
||||
// 打开远程文件
|
||||
remoteFile, err := s.Client.Open(remotePath)
|
||||
if err != nil {
|
||||
logger.Errorf("CopyFileRemoteToLocal failed to opening remote file: => %s", err.Error())
|
||||
return err
|
||||
}
|
||||
defer remoteFile.Close()
|
||||
|
||||
if err := os.MkdirAll(filepath.Dir(localPath), 0775); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 如果目标文件已经存在,先将目标文件重命名
|
||||
// if info, err := os.Stat(localPath); err == nil && !info.IsDir() {
|
||||
// ext := filepath.Ext(localPath)
|
||||
// name := localPath[0 : len(localPath)-len(ext)]
|
||||
// newName := fmt.Sprintf("%s-%s%s", name, time.Now().Format("20060102_150405"), ext)
|
||||
// err := os.Rename(localPath, newName)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// }
|
||||
|
||||
// 创建本地文件
|
||||
localFile, err := os.Create(localPath)
|
||||
if err != nil {
|
||||
logger.Errorf("CopyFileRemoteToLocal failed to creating local file: => %s", err.Error())
|
||||
return err
|
||||
}
|
||||
defer localFile.Close()
|
||||
|
||||
// 复制文件内容
|
||||
_, err = io.Copy(localFile, remoteFile)
|
||||
if err != nil {
|
||||
logger.Errorf("CopyFileRemoteToLocal failed to copying contents: => %s", err.Error())
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CopyDirRemoteToLocal 复制文件-本地到远程
|
||||
func (s *SSHClientSFTP) CopyFileLocalToRemote(localPath, remotePath string) error {
|
||||
// 打开本地文件
|
||||
localFile, err := os.Open(localPath)
|
||||
if err != nil {
|
||||
logger.Errorf("CopyFileLocalToRemote failed to opening local file: => %s", err.Error())
|
||||
return err
|
||||
}
|
||||
defer localFile.Close()
|
||||
|
||||
// 创建远程文件
|
||||
remoteFile, err := s.Client.Create(remotePath)
|
||||
if err != nil {
|
||||
logger.Errorf("CopyFileLocalToRemote failed to creating remote file: => %s", err.Error())
|
||||
return err
|
||||
}
|
||||
defer remoteFile.Close()
|
||||
|
||||
// 复制文件内容
|
||||
_, err = io.Copy(remoteFile, localFile)
|
||||
if err != nil {
|
||||
logger.Errorf("CopyFileLocalToRemote failed to copying contents: => %s", err.Error())
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,17 +1,16 @@
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/user"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"nms_cxy/src/framework/logger"
|
||||
"nms_cxy/src/framework/utils/cmd"
|
||||
|
||||
gosftp "github.com/pkg/sftp"
|
||||
gossh "golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
@@ -87,33 +86,6 @@ func (c *ConnSSH) Close() {
|
||||
}
|
||||
}
|
||||
|
||||
// NewClientByLocalPrivate 创建SSH客户端-本地私钥(~/.ssh/id_rsa)直连
|
||||
//
|
||||
// ssh.ConnSSH{
|
||||
// User: "user",
|
||||
// Addr: "192.168.x.x",
|
||||
// Port: body.Port,
|
||||
// }
|
||||
func (c *ConnSSH) NewClientByLocalPrivate() (*ConnSSH, error) {
|
||||
c.Port = 22
|
||||
c.AuthMode = "1"
|
||||
usr, err := user.Current()
|
||||
if err != nil {
|
||||
logger.Errorf("NewClientByLocal get current user => %s", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 读取用户默认的私钥文件
|
||||
keyPath := fmt.Sprintf("%s/.ssh/id_rsa", usr.HomeDir)
|
||||
key, err := os.ReadFile(keyPath)
|
||||
if err != nil {
|
||||
logger.Errorf("NewClientByLocal [%s] read private key => %s", usr.Username, err.Error())
|
||||
return nil, err
|
||||
}
|
||||
c.PrivateKey = string(key)
|
||||
return c.NewClient()
|
||||
}
|
||||
|
||||
// RunCMD 执行单次命令
|
||||
func (c *ConnSSH) RunCMD(cmd string) (string, error) {
|
||||
if c.Client == nil {
|
||||
@@ -135,7 +107,7 @@ func (c *ConnSSH) RunCMD(cmd string) (string, error) {
|
||||
return c.LastResult, err
|
||||
}
|
||||
|
||||
// NewClient 创建SSH客户端会话对象
|
||||
// NewClientSession 创建SSH客户端会话对象
|
||||
func (c *ConnSSH) NewClientSession(cols, rows int) (*SSHClientSession, error) {
|
||||
sshSession, err := c.Client.NewSession()
|
||||
if err != nil {
|
||||
@@ -172,75 +144,84 @@ func (c *ConnSSH) NewClientSession(cols, rows int) (*SSHClientSession, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// SSHClientSession SSH客户端会话对象
|
||||
type SSHClientSession struct {
|
||||
Stdin io.WriteCloser
|
||||
Stdout *singleWriter
|
||||
Session *gossh.Session
|
||||
// NewClientSFTP 创建SSH客户端SFTP对象
|
||||
func (c *ConnSSH) NewClientSFTP() (*SSHClientSFTP, error) {
|
||||
sftpClient, err := gosftp.NewClient(c.Client)
|
||||
if err != nil {
|
||||
logger.Errorf("NewClientSFTP failed to create sftp: => %s", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &SSHClientSFTP{
|
||||
Client: sftpClient,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Close 关闭会话
|
||||
func (s *SSHClientSession) Close() {
|
||||
if s.Stdin != nil {
|
||||
s.Stdin.Close()
|
||||
}
|
||||
if s.Stdout != nil {
|
||||
s.Stdout = nil
|
||||
}
|
||||
if s.Session != nil {
|
||||
s.Session.Close()
|
||||
// NewClientByLocalPrivate 创建SSH客户端-本地私钥(~/.ssh/id_rsa)直连
|
||||
//
|
||||
// ssh.ConnSSH{
|
||||
// User: "user",
|
||||
// Addr: "192.168.x.x",
|
||||
// Port: body.Port,
|
||||
// }
|
||||
func (c *ConnSSH) NewClientByLocalPrivate() (*ConnSSH, error) {
|
||||
c.Port = 22
|
||||
c.AuthMode = "1"
|
||||
privateKey, err := c.CurrentUserRsaKey(false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.PrivateKey = privateKey
|
||||
return c.NewClient()
|
||||
}
|
||||
|
||||
// Write 写入命令 回车(\n)才会执行
|
||||
func (s *SSHClientSession) Write(cmd string) (int, error) {
|
||||
if s.Stdin == nil {
|
||||
return 0, fmt.Errorf("ssh client session is nil to content write failed")
|
||||
}
|
||||
return s.Stdin.Write([]byte(cmd))
|
||||
}
|
||||
|
||||
// Read 读取结果 等待一会才有结果
|
||||
func (s *SSHClientSession) Read() []byte {
|
||||
if s.Stdout == nil {
|
||||
return []byte{}
|
||||
}
|
||||
// time.Sleep(300 * time.Millisecond)
|
||||
bs := s.Stdout.Bytes()
|
||||
if len(bs) > 0 {
|
||||
s.Stdout.Reset()
|
||||
return bs
|
||||
}
|
||||
return []byte{}
|
||||
}
|
||||
|
||||
// CombinedOutput 发送命令带结果返回
|
||||
func (s *SSHClientSession) CombinedOutput(cmd string) (string, error) {
|
||||
n, err := s.Write(cmd)
|
||||
if n == 0 || err != nil {
|
||||
// CurrentUserRsaKey 当前用户OMC使用的RSA私钥
|
||||
// 默认读取私钥
|
||||
// ssh-keygen -t rsa -P "" -f ~/.ssh/id_rsa
|
||||
// ssh-keygen -y -f ~/.ssh/id_rsa > ~/.ssh/id_rsa.pub
|
||||
func (c *ConnSSH) CurrentUserRsaKey(publicKey bool) (string, error) {
|
||||
usr, err := user.Current()
|
||||
if err != nil {
|
||||
logger.Errorf("CurrentUserRsaKey get => %s", err.Error())
|
||||
return "", err
|
||||
}
|
||||
return string(s.Read()), nil
|
||||
|
||||
// 是否存在私钥并创建
|
||||
keyPath := fmt.Sprintf("%s/.ssh/id_rsa", usr.HomeDir)
|
||||
if _, err := os.Stat(keyPath); err != nil {
|
||||
if _, err := cmd.ExecWithCheck("ssh-keygen", "-t", "rsa", "-P", "", "-f", keyPath); err != nil {
|
||||
logger.Errorf("CurrentUserPrivateKey ssh-keygen [%s] rsa => %s", usr.Username, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// 读取用户默认的文件
|
||||
if publicKey {
|
||||
keyPath = keyPath + ".pub"
|
||||
}
|
||||
key, err := os.ReadFile(keyPath)
|
||||
if err != nil {
|
||||
logger.Errorf("CurrentUserRsaKey [%s] read => %s", usr.Username, err.Error())
|
||||
return "", fmt.Errorf("read file %s fail", keyPath)
|
||||
}
|
||||
return string(key), nil
|
||||
}
|
||||
|
||||
// singleWriter SSH客户端会话消息
|
||||
type singleWriter struct {
|
||||
b bytes.Buffer
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func (w *singleWriter) Write(p []byte) (int, error) {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
return w.b.Write(p)
|
||||
}
|
||||
func (w *singleWriter) Bytes() []byte {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
return w.b.Bytes()
|
||||
}
|
||||
func (w *singleWriter) Reset() {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
w.b.Reset()
|
||||
// SendToAuthorizedKeys 发送当前用户私钥到远程服务器进行授权密钥
|
||||
func (c *ConnSSH) SendToAuthorizedKeys() error {
|
||||
publicKey, err := c.CurrentUserRsaKey(true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
authorizedKeysEntry := fmt.Sprintln(strings.TrimSpace(publicKey))
|
||||
cmdStrArr := []string{
|
||||
fmt.Sprintf("sudo mkdir -p /home/%s/.ssh && sudo chown %s:%s /home/%s/.ssh && sudo chmod 700 /home/%s/.ssh", c.User, c.User, c.User, c.User, c.User),
|
||||
fmt.Sprintf("sudo touch /home/%s/.ssh/authorized_keys && sudo chown %s:%s /home/%s/.ssh/authorized_keys && sudo chmod 600 /home/%s/.ssh/authorized_keys", c.User, c.User, c.User, c.User, c.User),
|
||||
fmt.Sprintf("echo '%s' | sudo tee -a /home/%s/.ssh/authorized_keys", authorizedKeysEntry, c.User),
|
||||
}
|
||||
_, err = c.RunCMD(strings.Join(cmdStrArr, " && "))
|
||||
if err != nil {
|
||||
logger.Errorf("SendAuthorizedKeys echo err %s", err.Error())
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
73
src/framework/utils/ssh/ssh_session.go
Normal file
73
src/framework/utils/ssh/ssh_session.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
gossh "golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
// SSHClientSession SSH客户端会话对象
|
||||
type SSHClientSession struct {
|
||||
Stdin io.WriteCloser
|
||||
Stdout *singleWriter
|
||||
Session *gossh.Session
|
||||
}
|
||||
|
||||
// Close 关闭会话
|
||||
func (s *SSHClientSession) Close() {
|
||||
if s.Stdin != nil {
|
||||
s.Stdin.Close()
|
||||
}
|
||||
if s.Stdout != nil {
|
||||
s.Stdout = nil
|
||||
}
|
||||
if s.Session != nil {
|
||||
s.Session.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// Write 写入命令 回车(\n)才会执行
|
||||
func (s *SSHClientSession) Write(cmd string) (int, error) {
|
||||
if s.Stdin == nil {
|
||||
return 0, fmt.Errorf("ssh client session is nil to content write failed")
|
||||
}
|
||||
return s.Stdin.Write([]byte(cmd))
|
||||
}
|
||||
|
||||
// Read 读取结果
|
||||
func (s *SSHClientSession) Read() []byte {
|
||||
if s.Stdout == nil {
|
||||
return []byte{}
|
||||
}
|
||||
bs := s.Stdout.Bytes()
|
||||
if len(bs) > 0 {
|
||||
s.Stdout.Reset()
|
||||
return bs
|
||||
}
|
||||
return []byte{}
|
||||
}
|
||||
|
||||
// singleWriter SSH客户端会话消息
|
||||
type singleWriter struct {
|
||||
b bytes.Buffer
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func (w *singleWriter) Write(p []byte) (int, error) {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
return w.b.Write(p)
|
||||
}
|
||||
func (w *singleWriter) Bytes() []byte {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
return w.b.Bytes()
|
||||
}
|
||||
func (w *singleWriter) Reset() {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
w.b.Reset()
|
||||
}
|
||||
77
src/framework/utils/telnet/parse.go
Normal file
77
src/framework/utils/telnet/parse.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package telnet
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ConvertToStr 转换为string
|
||||
func ConvertToStr(telnetClient *ConnTelnet, cmd string) (string, error) {
|
||||
output, err := telnetClient.RunCMD(cmd)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
str := strings.ToLower(output)
|
||||
|
||||
// 截断
|
||||
index := strings.Index(str, "\n")
|
||||
if index != -1 {
|
||||
str = str[:index]
|
||||
}
|
||||
|
||||
// 命令成功
|
||||
if strings.Contains(str, "ok") || strings.Contains(str, "success") {
|
||||
return str, nil
|
||||
}
|
||||
|
||||
return "", fmt.Errorf(str)
|
||||
}
|
||||
|
||||
// ConvertToMap 转换为map
|
||||
func ConvertToMap(telnetClient *ConnTelnet, cmd string) (map[string]string, error) {
|
||||
output, err := telnetClient.RunCMD(cmd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 无数据
|
||||
if strings.HasPrefix(output, "No ") {
|
||||
// 截断
|
||||
index := strings.Index(output, "\n")
|
||||
if index != -1 {
|
||||
output = output[:index]
|
||||
}
|
||||
return nil, fmt.Errorf(output)
|
||||
}
|
||||
|
||||
// 初始化一个map用于存储拆分后的键值对
|
||||
m := make(map[string]string)
|
||||
|
||||
var items []string
|
||||
if strings.Contains(output, "\r\n") {
|
||||
// 按照分隔符"\r\n"进行拆分
|
||||
items = strings.Split(output, "\r\n")
|
||||
} else if strings.Contains(output, "\n") {
|
||||
// 按照分隔符"\n"进行拆分
|
||||
items = strings.Split(output, "\n")
|
||||
}
|
||||
|
||||
// 遍历拆分后的结果
|
||||
for _, item := range items {
|
||||
var pair []string
|
||||
|
||||
if strings.Contains(item, "=") {
|
||||
// 按照分隔符"="进行拆分键值对
|
||||
pair = strings.SplitN(item, "=", 2)
|
||||
} else if strings.Contains(item, ":") {
|
||||
// 按照分隔符":"进行拆分键值对
|
||||
pair = strings.SplitN(item, ":", 2)
|
||||
}
|
||||
|
||||
if len(pair) == 2 {
|
||||
// 将键值对存入map中
|
||||
m[pair[0]] = pair[1]
|
||||
}
|
||||
}
|
||||
return m, err
|
||||
}
|
||||
@@ -51,6 +51,12 @@ func (c *ConnTelnet) NewClient() (*ConnTelnet, error) {
|
||||
// fmt.Fprintln(client, c.Password)
|
||||
|
||||
c.Client = &client
|
||||
|
||||
// 调整窗口大小 (120 列 x 128 行)
|
||||
requestPty(c.Client, 120, 128)
|
||||
|
||||
// 排空连接登录的信息
|
||||
c.RunCMD("")
|
||||
return c, nil
|
||||
}
|
||||
|
||||
@@ -67,50 +73,32 @@ func (c *ConnTelnet) RunCMD(cmd string) (string, error) {
|
||||
return "", fmt.Errorf("telnet client not connected")
|
||||
}
|
||||
conn := *c.Client
|
||||
var buf bytes.Buffer
|
||||
tmp := make([]byte, 1024)
|
||||
|
||||
// 排空连接登录的信息
|
||||
for {
|
||||
// 设置读取超时时间为100毫秒
|
||||
conn.SetReadDeadline(time.Now().Add(100 * time.Millisecond))
|
||||
n, err := conn.Read(tmp)
|
||||
if err != nil {
|
||||
// 判断是否是超时错误
|
||||
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
|
||||
break
|
||||
}
|
||||
break
|
||||
}
|
||||
if n == 0 {
|
||||
break
|
||||
}
|
||||
buf.Write(tmp[:n])
|
||||
}
|
||||
buf.Reset()
|
||||
|
||||
// 写入命令
|
||||
_, err := conn.Write([]byte(cmd))
|
||||
if err != nil {
|
||||
return "", err
|
||||
if cmd != "" {
|
||||
if _, err := conn.Write([]byte(cmd)); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
// 读取本次响应命令消息
|
||||
var buf bytes.Buffer
|
||||
tmp := make([]byte, 1024)
|
||||
for {
|
||||
// 设置读取超时时间为1000毫秒
|
||||
conn.SetReadDeadline(time.Now().Add(1000 * time.Millisecond))
|
||||
// 读取命令消息
|
||||
n, err := conn.Read(tmp)
|
||||
if err != nil {
|
||||
// 判断是否是超时错误
|
||||
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
|
||||
break
|
||||
}
|
||||
if n == 0 || err != nil {
|
||||
tmp = nil
|
||||
break
|
||||
}
|
||||
if n == 0 {
|
||||
|
||||
tmpStr := string(tmp[:n])
|
||||
buf.WriteString(tmpStr)
|
||||
|
||||
// 是否有终止符
|
||||
if strings.HasSuffix(tmpStr, ">") || strings.HasSuffix(tmpStr, "> ") || strings.HasSuffix(tmpStr, "# ") {
|
||||
tmp = nil
|
||||
break
|
||||
}
|
||||
buf.Write(tmp[:n])
|
||||
}
|
||||
defer buf.Reset()
|
||||
|
||||
@@ -119,100 +107,24 @@ func (c *ConnTelnet) RunCMD(cmd string) (string, error) {
|
||||
}
|
||||
|
||||
// NewClient 创建Telnet客户端会话对象
|
||||
func (c *ConnTelnet) NewClientSession() (*TelnetClientSession, error) {
|
||||
func (c *ConnTelnet) NewClientSession(cols, rows int) (*TelnetClientSession, error) {
|
||||
if c.Client == nil {
|
||||
return nil, fmt.Errorf("telnet client not connected")
|
||||
}
|
||||
conn := *c.Client
|
||||
|
||||
var buf bytes.Buffer
|
||||
tmp := make([]byte, 1024)
|
||||
// 排空连接登录的信息
|
||||
for {
|
||||
// 设置读取超时时间为5毫秒
|
||||
conn.SetReadDeadline(time.Now().Add(5 * time.Millisecond))
|
||||
n, err := conn.Read(tmp)
|
||||
if err != nil {
|
||||
// 判断是否是超时错误
|
||||
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
|
||||
break
|
||||
}
|
||||
break
|
||||
}
|
||||
if n == 0 {
|
||||
break
|
||||
}
|
||||
buf.Write(tmp[:n])
|
||||
}
|
||||
buf.Reset()
|
||||
|
||||
requestPty(c.Client, cols, rows)
|
||||
return &TelnetClientSession{
|
||||
Client: conn,
|
||||
Client: *c.Client,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// TelnetClientSession Telnet客户端会话对象
|
||||
type TelnetClientSession struct {
|
||||
Client net.Conn
|
||||
}
|
||||
|
||||
// Close 关闭会话
|
||||
func (s *TelnetClientSession) Close() {
|
||||
if s.Client != nil {
|
||||
s.Client.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// Write 写入命令 不带回车(\n)也会执行根据客户端情况
|
||||
func (s *TelnetClientSession) Write(cmd string) (int, error) {
|
||||
if s.Client == nil {
|
||||
return 0, fmt.Errorf("client is nil to content write failed")
|
||||
}
|
||||
return s.Client.Write([]byte(cmd))
|
||||
}
|
||||
|
||||
// Read 读取结果 等待一会才有结果
|
||||
func (s *TelnetClientSession) Read() []byte {
|
||||
if s.Client == nil {
|
||||
return []byte{}
|
||||
}
|
||||
|
||||
buf := make([]byte, 1024)
|
||||
// 设置读取超时时间为100毫秒
|
||||
s.Client.SetReadDeadline(time.Now().Add(100 * time.Millisecond))
|
||||
_, err := s.Client.Read(buf)
|
||||
if err != nil {
|
||||
return []byte{}
|
||||
}
|
||||
return buf
|
||||
}
|
||||
|
||||
// CombinedOutput 发送命令带结果返回
|
||||
func (s *TelnetClientSession) CombinedOutput(cmd string) (string, error) {
|
||||
n, err := s.Write(cmd)
|
||||
if n == 0 || err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
tmp := make([]byte, 1024)
|
||||
for {
|
||||
// 设置读取超时时间为1000毫秒
|
||||
s.Client.SetReadDeadline(time.Now().Add(1000 * time.Millisecond))
|
||||
n, err := s.Client.Read(tmp)
|
||||
if err != nil {
|
||||
// 判断是否是超时错误
|
||||
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
|
||||
break
|
||||
}
|
||||
break
|
||||
}
|
||||
if n == 0 {
|
||||
break
|
||||
}
|
||||
buf.Write(tmp[:n])
|
||||
}
|
||||
defer buf.Reset()
|
||||
|
||||
return buf.String(), nil
|
||||
// requestPty 调整终端窗口大小
|
||||
func requestPty(client *net.Conn, cols, rows int) error {
|
||||
if client == nil {
|
||||
return fmt.Errorf("telnet client not connected")
|
||||
}
|
||||
conn := *client
|
||||
// 需要确保接收方理解并正确处理发送窗口大小设置命令
|
||||
conn.Write([]byte{255, 251, 31})
|
||||
conn.Write([]byte{255, 250, 31, byte(cols >> 8), byte(cols & 0xFF), byte(rows >> 8), byte(rows & 0xFF), 255, 240})
|
||||
return nil
|
||||
}
|
||||
|
||||
74
src/framework/utils/telnet/telnet_session.go
Normal file
74
src/framework/utils/telnet/telnet_session.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package telnet
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TelnetClientSession Telnet客户端会话对象
|
||||
type TelnetClientSession struct {
|
||||
Client net.Conn
|
||||
}
|
||||
|
||||
// Close 关闭会话
|
||||
func (s *TelnetClientSession) Close() {
|
||||
if s.Client != nil {
|
||||
s.Client.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// Write 写入命令 不带回车(\n)也会执行根据客户端情况
|
||||
func (s *TelnetClientSession) Write(cmd string) (int, error) {
|
||||
if s.Client == nil {
|
||||
return 0, fmt.Errorf("client is nil to content write failed")
|
||||
}
|
||||
return s.Client.Write([]byte(cmd))
|
||||
}
|
||||
|
||||
// Read 读取结果 等待一会才有结果
|
||||
func (s *TelnetClientSession) Read() []byte {
|
||||
if s.Client == nil {
|
||||
return []byte{}
|
||||
}
|
||||
|
||||
buf := make([]byte, 1024)
|
||||
// 设置读取超时时间为100毫秒
|
||||
s.Client.SetReadDeadline(time.Now().Add(100 * time.Millisecond))
|
||||
_, err := s.Client.Read(buf)
|
||||
if err != nil {
|
||||
return []byte{}
|
||||
}
|
||||
return buf
|
||||
}
|
||||
|
||||
// CombinedOutput 发送命令带结果返回
|
||||
func (s *TelnetClientSession) CombinedOutput(cmd string) (string, error) {
|
||||
n, err := s.Write(cmd)
|
||||
if n == 0 || err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
tmp := make([]byte, 1024)
|
||||
for {
|
||||
// 设置读取超时时间为1000毫秒
|
||||
s.Client.SetReadDeadline(time.Now().Add(1000 * time.Millisecond))
|
||||
n, err := s.Client.Read(tmp)
|
||||
if err != nil {
|
||||
// 判断是否是超时错误
|
||||
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
|
||||
break
|
||||
}
|
||||
break
|
||||
}
|
||||
if n == 0 {
|
||||
break
|
||||
}
|
||||
buf.Write(tmp[:n])
|
||||
}
|
||||
defer buf.Reset()
|
||||
|
||||
return buf.String(), nil
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"nms_cxy/src/framework/logger"
|
||||
redisCahe "nms_cxy/src/framework/redis"
|
||||
"nms_cxy/src/framework/utils/generate"
|
||||
"nms_cxy/src/framework/utils/machine"
|
||||
"nms_cxy/src/framework/vo"
|
||||
|
||||
jwt "github.com/golang-jwt/jwt/v5"
|
||||
@@ -74,7 +75,7 @@ func Create(loginUser *vo.LoginUser, ilobArgs ...string) string {
|
||||
|
||||
// 生成令牌设置密钥
|
||||
secret := config.Get("jwt.secret").(string)
|
||||
tokenStr, err := jwtToken.SignedString([]byte(secret))
|
||||
tokenStr, err := jwtToken.SignedString([]byte(machine.Code + "@" + secret))
|
||||
if err != nil {
|
||||
logger.Infof("jwt sign err : %v", err)
|
||||
return ""
|
||||
@@ -118,7 +119,7 @@ func Verify(tokenString string) (jwt.MapClaims, error) {
|
||||
// 判断加密算法是预期的加密算法
|
||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); ok {
|
||||
secret := config.Get("jwt.secret").(string)
|
||||
return []byte(secret), nil
|
||||
return []byte(machine.Code + "@" + secret), nil
|
||||
}
|
||||
return nil, jwt.ErrSignatureInvalid
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user