feat: 文件备份/CDR/LOG本地文件列表功能接口

This commit is contained in:
TsMask
2025-05-09 18:31:23 +08:00
parent 5e7c9bb7e0
commit 2f27466408
15 changed files with 1287 additions and 73 deletions

103
src/framework/ssh/files.go Normal file
View File

@@ -0,0 +1,103 @@
package ssh
import (
"fmt"
"strings"
"be.ems/src/framework/logger"
"be.ems/src/framework/utils/cmd"
"be.ems/src/framework/utils/parse"
)
// FileListRow 文件列表行数据
type FileListRow struct {
FileType string `json:"fileType"` // 文件类型 dir, file, symlink
FileMode string `json:"fileMode"` // 文件的权限
LinkCount int64 `json:"linkCount"` // 硬链接数目
Owner string `json:"owner"` // 所属用户
Group string `json:"group"` // 所属组
Size int64 `json:"size"` // 文件的大小
ModifiedTime int64 `json:"modifiedTime"` // 最后修改时间,单位为秒
FileName string `json:"fileName"` // 文件的名称
}
// 文件列表
// search 文件名后模糊*
//
// return 行记录,异常
func FileList(sshClient *ConnSSH, path, search string) ([]FileListRow, error) {
var rows []FileListRow
rowStr := ""
// 发送命令
searchStr := "*"
if search != "" {
searchStr = search + searchStr
}
// cd /var/log && find. -maxdepth 1 -name'mme*' -exec ls -ltd --time-style=+%s {} +
cmdStr := fmt.Sprintf("cd %s && find . -maxdepth 1 -name '%s' -exec ls -ltd --time-style=+%%s {} +", path, searchStr)
// cd /var/log && ls -ltd --time-style=+%s mme*
// cmdStr := fmt.Sprintf("cd %s && ls -ltd --time-style=+%%s %s", path, searchStr)
// 是否远程客户端读取
if sshClient == nil {
resultStr, err := cmd.Exec(cmdStr)
if err != nil {
logger.Errorf("Ne FileList Path: %s, Search: %s, Error:%s", path, search, err.Error())
return rows, err
}
rowStr = resultStr
} else {
resultStr, err := sshClient.RunCMD(cmdStr)
if err != nil {
logger.Errorf("Ne FileList Path: %s, Search: %s, Error:%s", path, search, err.Error())
return rows, err
}
rowStr = resultStr
}
// 遍历组装
rowStrList := strings.Split(rowStr, "\n")
for _, rowStr := range rowStrList {
if rowStr == "" {
continue
}
// 使用空格对字符串进行切割
fields := strings.Fields(rowStr)
// 拆分不足7位跳过
if len(fields) != 7 {
continue
}
// 文件类型
fileMode := fields[0]
fileType := "file"
if fileMode[0] == 'd' {
fileType = "dir"
} else if fileMode[0] == 'l' {
fileType = "symlink"
}
// 文件名
fileName := fields[6]
if fileName == "." {
continue
} else if strings.HasPrefix(fileName, "./") {
fileName = strings.TrimPrefix(fileName, "./")
}
// 提取各个字段的值
rows = append(rows, FileListRow{
FileMode: fileMode,
FileType: fileType,
LinkCount: parse.Number(fields[1]),
Owner: fields[2],
Group: fields[3],
Size: parse.Number(fields[4]),
ModifiedTime: parse.Number(fields[5]),
FileName: fileName,
})
}
return rows, nil
}

157
src/framework/ssh/sftp.go Normal file
View File

@@ -0,0 +1,157 @@
package ssh
import (
"io"
"os"
"path/filepath"
"be.ems/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 {
// 创建本地目录
err := os.MkdirAll(localDir, 0775)
if err != nil {
logger.Errorf("CopyDirRemoteToLocal failed to creating local directory %s: => %s", localDir, err.Error())
return err
}
// 列出远程目录中的文件和子目录
remoteFiles, err := s.Client.ReadDir(remoteDir)
if err != nil {
logger.Errorf("CopyDirRemoteToLocal failed to reading remote directory %s: => %s", remoteDir, err.Error())
return err
}
// 遍历远程文件和子目录并复制到本地
for _, remoteFile := range remoteFiles {
remotePath := filepath.ToSlash(filepath.Join(remoteDir, remoteFile.Name()))
localPath := filepath.ToSlash(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 {
// 如果是文件,则复制文件内容
if err := s.CopyFileRemoteToLocal(remotePath, localPath); err != nil {
logger.Errorf("CopyDirRemoteToLocal failed to opening remote file %s: => %s", remotePath, err.Error())
continue
}
}
}
return nil
}
// CopyDirLocalToRemote 复制目录-本地到远程
func (s *SSHClientSFTP) CopyDirLocalToRemote(localDir, remoteDir string) error {
// 遍历本地目录中的文件和子目录并复制到远程
err := filepath.Walk(localDir, func(localPath string, info os.FileInfo, err error) error {
if err != nil {
return err
}
// 生成远程路径
remotePath := filepath.ToSlash(filepath.Join(remoteDir, localPath[len(localDir):]))
if info.IsDir() {
// 如果是子目录,则创建远程目录
if err := s.Client.MkdirAll(remotePath); err != nil {
logger.Errorf("CopyDirLocalToRemote failed to creating remote directory %s: => %s", remotePath, err.Error())
return err
}
} else {
// 如果是文件,则复制文件内容
if err := s.CopyFileLocalToRemote(localPath, remotePath); err != nil {
logger.Errorf("CopyDirLocalToRemote failed to copying remote file %s: => %s", localPath, err.Error())
return err
}
}
return nil
})
if err != nil {
logger.Errorf("CopyDirLocalToRemote failed to walking remote directory: => %s", err.Error())
return err
}
return nil
}
// CopyFileRemoteToLocal 复制文件-远程到本地
func (s *SSHClientSFTP) CopyFileRemoteToLocal(remotePath, localPath string) error {
if err := os.MkdirAll(filepath.Dir(localPath), 0775); err != nil {
return err
}
// 打开远程文件
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()
// 创建本地文件
localFile, err := os.Create(localPath)
if err != nil {
logger.Errorf("CopyFileRemoteToLocal failed to creating local file: => %s", err.Error())
return err
}
defer localFile.Close()
// 复制文件内容
if _, err = io.Copy(localFile, remoteFile); err != nil {
logger.Errorf("CopyFileRemoteToLocal failed to copying contents: => %s", err.Error())
return err
}
return nil
}
// CopyFileLocalToRemote 复制文件-本地到远程
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()
// 创建远程目录
if err := s.Client.MkdirAll(filepath.Dir(remotePath)); err != nil {
logger.Errorf("CopyFileLocalToRemote failed to creating remote directory %s: => %s", remotePath, err.Error())
return err
}
// 创建远程文件
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()
// 复制文件内容
if _, err = io.Copy(remoteFile, localFile); err != nil {
logger.Errorf("CopyFileLocalToRemote failed to copying contents: => %s", err.Error())
return err
}
return nil
}

229
src/framework/ssh/ssh.go Normal file
View File

@@ -0,0 +1,229 @@
package ssh
import (
"fmt"
"os"
"os/user"
"strings"
"time"
"be.ems/src/framework/logger"
"be.ems/src/framework/utils/cmd"
gosftp "github.com/pkg/sftp"
gossh "golang.org/x/crypto/ssh"
)
// ConnSSH 连接SSH对象
type ConnSSH struct {
User string `json:"user"` // 主机用户名
Addr string `json:"addr"` // 主机地址
Port int64 `json:"port"` // SSH端口
AuthMode string `json:"authMode"` // 认证模式0密码 1主机私钥
Password string `json:"password"` // 认证密码
PrivateKey string `json:"privateKey"` // 认证私钥
PassPhrase string `json:"passPhrase"` // 认证私钥密码
DialTimeOut time.Duration `json:"dialTimeOut"` // 连接超时断开
Client *gossh.Client `json:"client"`
LastResult string `json:"lastResult"` // 记最后一次执行命令的结果
}
// NewClient 创建SSH客户端
func (c *ConnSSH) NewClient() (*ConnSSH, error) {
// IPV6地址协议
proto := "tcp"
if strings.Contains(c.Addr, ":") {
proto = "tcp6"
c.Addr = fmt.Sprintf("[%s]", c.Addr)
}
addr := fmt.Sprintf("%s:%d", c.Addr, c.Port)
// ssh客户端配置
config := &gossh.ClientConfig{}
config.SetDefaults()
config.HostKeyCallback = gossh.InsecureIgnoreHostKey()
config.User = c.User
// 默认等待5s
if c.DialTimeOut == 0 {
c.DialTimeOut = 5 * time.Second
}
config.Timeout = c.DialTimeOut
// 认证模式-0密码 1私钥
if c.AuthMode == "1" {
var signer gossh.Signer
var err error
if len(c.PassPhrase) != 0 {
signer, err = gossh.ParsePrivateKeyWithPassphrase([]byte(c.PrivateKey), []byte(c.PassPhrase))
} else {
signer, err = gossh.ParsePrivateKey([]byte(c.PrivateKey))
}
if err != nil {
logger.Errorf("NewClient parse private key => %s", err.Error())
return nil, err
}
config.Auth = []gossh.AuthMethod{gossh.PublicKeys(signer)}
} else {
config.Auth = []gossh.AuthMethod{gossh.Password(c.Password)}
}
client, err := gossh.Dial(proto, addr, config)
if nil != err {
logger.Errorf("NewClient dial => %s", err.Error())
return c, err
}
c.Client = client
return c, nil
}
// Close 关闭当前SSH客户端
func (c *ConnSSH) Close() {
if c.Client != nil {
c.Client.Close()
}
}
// RunCMD 执行单次命令
func (c *ConnSSH) RunCMD(cmd string) (string, error) {
if c.Client == nil {
if _, err := c.NewClient(); err != nil {
return "", err
}
}
session, err := c.Client.NewSession()
if err != nil {
logger.Errorf("RunCMD failed to create session: => %s", err.Error())
return "", err
}
defer session.Close()
buf, err := session.CombinedOutput(cmd)
if err != nil {
logger.Infof("RunCMD failed run command: => %s", cmd)
logger.Errorf("RunCMD failed run command: => %s", err.Error())
}
c.LastResult = string(buf)
return c.LastResult, err
}
// NewClientSession 创建SSH客户端会话对象
func (c *ConnSSH) NewClientSession(cols, rows int) (*SSHClientSession, error) {
sshSession, err := c.Client.NewSession()
if err != nil {
return nil, err
}
stdin, err := sshSession.StdinPipe()
if err != nil {
return nil, err
}
comboWriter := new(singleWriter)
sshSession.Stdout = comboWriter
sshSession.Stderr = comboWriter
modes := gossh.TerminalModes{
gossh.ECHO: 1,
gossh.TTY_OP_ISPEED: 14400,
gossh.TTY_OP_OSPEED: 14400,
}
if err := sshSession.RequestPty("xterm", rows, cols, modes); err != nil {
return nil, err
}
if err := sshSession.Shell(); err != nil {
return nil, err
}
return &SSHClientSession{
Stdin: stdin,
Stdout: comboWriter,
Session: sshSession,
}, nil
}
// 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
}
// NewClientByLocalPrivate 创建SSH客户端-本地私钥(~/.ssh/id_rsa)直连
//
// ssh.ConnSSH{
// User: "user",
// Addr: "192.168.x.x",
// Port: body.Port,
// }
func (c *ConnSSH) NewClientByLocalPrivate() (*ConnSSH, error) {
c.AuthMode = "1"
privateKey, err := c.CurrentUserRsaKey(false)
if err != nil {
return nil, err
}
c.PrivateKey = privateKey
return c.NewClient()
}
// 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
}
// 是否存在私钥并创建
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
}
// SendToAuthorizedKeys 发送当前用户私钥到远程服务器进行授权密钥
func (c *ConnSSH) SendToAuthorizedKeys() error {
publicKey, err := c.CurrentUserRsaKey(true)
if err != nil {
return err
}
// "sudo mkdir -p ~/.ssh && sudo chown omcuser:omcuser ~/.ssh && sudo chmod 700 ~/.ssh"
// "sudo touch ~/.ssh/authorized_keys && sudo chown omcuser:omcuser ~/.ssh/authorized_keys && sudo chmod 600 ~/.ssh/authorized_keys"
// "echo 'ssh-rsa AAAAB3= pc-host\n' | sudo tee -a ~/.ssh/authorized_keys"
authorizedKeysEntry := fmt.Sprintln(strings.TrimSpace(publicKey))
cmdStrArr := []string{
fmt.Sprintf("sudo mkdir -p ~/.ssh && sudo chown %s:%s ~/.ssh && sudo chmod 700 ~/.ssh", c.User, c.User),
fmt.Sprintf("sudo touch ~/.ssh/authorized_keys && sudo chown %s:%s ~/.ssh/authorized_keys && sudo chmod 600 ~/.ssh/authorized_keys", c.User, c.User),
fmt.Sprintf("echo '%s' | sudo tee -a ~/.ssh/authorized_keys", authorizedKeysEntry),
}
_, err = c.RunCMD(strings.Join(cmdStrArr, " && "))
if err != nil {
logger.Errorf("SendAuthorizedKeys echo err %s", err.Error())
return err
}
return nil
}

View 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()
}

View File

@@ -152,3 +152,112 @@ func WriteSheet(headerCells map[string]string, dataCells []map[string]any, fileN
}
return saveFilePath, nil
}
// WriterFileExecl 写入xlsx文件
//
// 例如:
// headerCells := map[string]string{"A1": "姓名", "B1": "年龄", "C1": "城市"}
//
// dataCells := []map[string]any{
// {"A2": "1", "B2": "2", "C2": "3"},
// }
//
// filePath := "example.xlsx"
// err := file.WriterFileExecl(headerCells, dataCells, filePath, "Sheet1")
func WriterFileExecl(headerCells map[string]string, dataCells []map[string]any, filePath, sheetName string) error {
f := excelize.NewFile()
defer func() {
if err := f.Close(); err != nil {
logger.Errorf("WriterFileExecl to close worksheet file: %v", err)
}
}()
// 创建一个工作表
if sheetName == "" {
sheetName = "Sheet1"
}
index, err := f.NewSheet(sheetName)
if err != nil {
return fmt.Errorf("failed to create worksheet %v", err)
}
// 设置工作簿的默认工作表
f.SetActiveSheet(index)
// 首个和最后key名称
firstKey := "A"
lastKey := "B"
// 第一行表头标题
for key, title := range headerCells {
f.SetCellValue(sheetName, key, title)
if key[:1] > lastKey {
lastKey = key[:1]
}
}
// 设置工作表上宽度为 20
f.SetColWidth(sheetName, firstKey, lastKey, 20)
// 从第二行开始的数据
for _, cell := range dataCells {
for key, value := range cell {
f.SetCellValue(sheetName, key, value)
}
}
// 创建文件目录
if err := os.MkdirAll(filepath.Dir(filePath), 0775); err != nil {
return fmt.Errorf("failed to create save file %v", err)
}
// 根据指定路径保存文件
if err := f.SaveAs(filePath); err != nil {
return fmt.Errorf("failed to save worksheet %v", err)
}
return nil
}
// ReadFileExecl 读取xlsx文件 转换数组数据
func ReadFileExecl(filePath, sheetName string) ([]map[string]string, error) {
data := make([]map[string]string, 0)
// 打开 Excel 文件
f, err := excelize.OpenFile(filePath)
if err != nil {
return data, err
}
defer func() {
if err := f.Close(); err != nil {
logger.Errorf("ReadSheet to close worksheet file : %v", err)
}
}()
// 检查工作簿是否存在
if sheetName == "" {
sheetName = "Sheet1"
}
if visible, _ := f.GetSheetVisible(sheetName); !visible {
return data, fmt.Errorf("failed to read workbook %s", sheetName)
}
// 获取工作簿记录
rows, err := f.GetRows(sheetName)
if err != nil {
return data, err
}
for i, row := range rows {
// 跳过第一行
if i == 0 {
continue
}
// 遍历每个单元格
rowData := map[string]string{}
for columnIndex, cellValue := range row {
columnName, _ := excelize.ColumnNumberToName(columnIndex + 1)
rowData[columnName] = cellValue
}
data = append(data, rowData)
}
return data, nil
}

View File

@@ -12,7 +12,7 @@ import (
"time"
"be.ems/src/framework/config"
"be.ems/src/framework/constants/uploadsubpath"
"be.ems/src/framework/constants"
"be.ems/src/framework/logger"
"be.ems/src/framework/utils/date"
"be.ems/src/framework/utils/generate"
@@ -237,7 +237,7 @@ func TransferChunkUploadFile(file *multipart.FileHeader, index, identifier strin
// 上传资源路径
prefix, dir := resourceUpload()
// 新文件名称并组装文件地址
filePath := filepath.Join(uploadsubpath.CHUNK, date.ParseDatePath(time.Now()), identifier)
filePath := filepath.Join(constants.UPLOAD_CHUNK, date.ParseDatePath(time.Now()), identifier)
writePathFile := filepath.Join(dir, filePath, index)
// 存入新文件路径
err = transferToNewFile(file, writePathFile)
@@ -261,7 +261,7 @@ func ChunkCheckFile(identifier, originalFileName string) ([]string, error) {
}
// 上传资源路径
_, dir := resourceUpload()
dirPath := path.Join(uploadsubpath.CHUNK, date.ParseDatePath(time.Now()), identifier)
dirPath := path.Join(constants.UPLOAD_CHUNK, date.ParseDatePath(time.Now()), identifier)
readPath := path.Join(dir, dirPath)
fileList, err := getDirFileNameList(readPath)
if err != nil {
@@ -286,7 +286,7 @@ func ChunkMergeFile(identifier, originalFileName, subPath string) (string, error
// 上传资源路径
prefix, dir := resourceUpload()
// 切片存放目录
dirPath := path.Join(uploadsubpath.CHUNK, date.ParseDatePath(time.Now()), identifier)
dirPath := path.Join(constants.UPLOAD_CHUNK, date.ParseDatePath(time.Now()), identifier)
readPath := path.Join(dir, dirPath)
// 组合存放文件路径
fileName := generateFileName(originalFileName)

View File

@@ -0,0 +1,78 @@
package file
import (
"os"
"path/filepath"
)
// FileListRow 文件列表行数据
type FileListRow struct {
FileType string `json:"fileType"` // 文件类型 dir, file, symlink
FileMode string `json:"fileMode"` // 文件的权限
LinkCount int64 `json:"linkCount"` // 硬链接数目
Owner string `json:"owner"` // 所属用户
Group string `json:"group"` // 所属组
Size int64 `json:"size"` // 文件的大小
ModifiedTime int64 `json:"modifiedTime"` // 最后修改时间,单位为秒
FileName string `json:"fileName"` // 文件的名称
}
// 文件列表
// search 文件名后模糊*
//
// return 行记录,异常
func FileList(path, search string) ([]FileListRow, error) {
var rows []FileListRow
// 构建搜索模式
pattern := "*"
if search != "" {
pattern = search + pattern
}
// 读取目录内容
entries, err := os.ReadDir(path)
if err != nil {
return nil, err
}
// 遍历目录项
for _, entry := range entries {
// 匹配文件名
matched, err := filepath.Match(pattern, entry.Name())
if err != nil || !matched {
continue
}
// 获取文件详细信息
info, err := entry.Info()
if err != nil {
continue
}
// 确定文件类型
fileType := "file"
if info.IsDir() {
fileType = "dir"
} else if info.Mode()&os.ModeSymlink != 0 {
fileType = "symlink"
}
// 获取系统特定的文件信息
linkCount, owner, group := getFileInfo(info)
// 组装文件信息
rows = append(rows, FileListRow{
FileMode: info.Mode().String(),
FileType: fileType,
LinkCount: linkCount,
Owner: owner,
Group: group,
Size: info.Size(),
ModifiedTime: info.ModTime().UnixMilli(),
FileName: entry.Name(),
})
}
return rows, nil
}

View File

@@ -0,0 +1,36 @@
//go:build !windows
// +build !windows
package file
import (
"fmt"
"os"
"os/user"
"syscall"
)
// getFileInfo 获取系统特定的文件信息s
func getFileInfo(info os.FileInfo) (linkCount int64, owner, group string) {
// Unix-like 系统 (Linux, macOS)
if stat, ok := info.Sys().(*syscall.Stat_t); ok {
// 获取用户名
ownerName := "root"
if stat.Uid != 0 {
if u, err := user.LookupId(fmt.Sprint(stat.Uid)); err == nil {
ownerName = u.Username
}
}
// 获取组名
groupName := "root"
if stat.Gid != 0 {
if g, err := user.LookupGroupId(fmt.Sprint(stat.Gid)); err == nil {
groupName = g.Name
}
}
return int64(stat.Nlink), ownerName, groupName
}
return 1, "", ""
}

View File

@@ -0,0 +1,13 @@
//go:build windows
// +build windows
package file
import (
"os"
)
// getFileInfo 获取系统特定的文件信息
func getFileInfo(_ os.FileInfo) (linkCount int64, owner, group string) {
return 1, "Administrator", "Administrators"
}