Files
RUSTDESK-AP-SERVER-SUNLIX/service/clientConfig.go

233 lines
7.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package service
import (
"archive/zip"
"crypto/md5"
"encoding/hex"
"fmt"
"io"
"os"
"path/filepath"
"time"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/model"
"github.com/lejianwen/rustdesk-api/v2/utils"
"gorm.io/gorm"
)
type ClientConfigService struct {
}
// GenerateClientConfig генерирует конфигурационный файл клиента
// customConfigFile - опциональный кастомный конфигурационный файл (если пустой, генерируется автоматически)
func (ccs *ClientConfigService) GenerateClientConfig(userId uint, password, description, customConfigFile string) (*model.ClientConfig, error) {
// Создаем директорию для хранения клиентов
clientsDir := filepath.Join(global.Config.Gin.ResourcesPath, "clients")
if err := os.MkdirAll(clientsDir, 0755); err != nil {
return nil, fmt.Errorf("failed to create clients directory: %v", err)
}
// Генерируем уникальное имя файла
timestamp := time.Now().Format("20060102150405")
fileName := fmt.Sprintf("rustdesk-client-%d-%s.zip", userId, timestamp)
filePath := filepath.Join(clientsDir, fileName)
// Создаем ZIP архив
zipFile, err := os.Create(filePath)
if err != nil {
return nil, fmt.Errorf("failed to create zip file: %v", err)
}
defer zipFile.Close()
zipWriter := zip.NewWriter(zipFile)
defer zipWriter.Close()
// Создаем конфигурационный файл TOML
var configContent string
if customConfigFile != "" {
// Используем кастомный конфигурационный файл
configContent = customConfigFile
} else {
// Генерируем автоматически
configContent = ccs.generateTomlConfig()
}
configFile, err := zipWriter.Create("RustDesk.toml")
if err != nil {
return nil, fmt.Errorf("failed to create config file in zip: %v", err)
}
_, err = configFile.Write([]byte(configContent))
if err != nil {
return nil, fmt.Errorf("failed to write config to zip: %v", err)
}
// Создаем файл с паролем (опционально, для справки)
if password != "" {
passwordFile, err := zipWriter.Create("password.txt")
if err == nil {
passwordFile.Write([]byte(fmt.Sprintf("Password: %s\n", password)))
}
}
// Получаем информацию о файле
fileInfo, err := os.Stat(filePath)
if err != nil {
return nil, fmt.Errorf("failed to get file info: %v", err)
}
// Вычисляем хеш файла
fileHash, err := ccs.calculateFileHash(filePath)
if err != nil {
return nil, fmt.Errorf("failed to calculate file hash: %v", err)
}
// Хешируем пароль для хранения в БД
hashedPassword, err := utils.EncryptPassword(password)
if err != nil {
return nil, fmt.Errorf("failed to hash password: %v", err)
}
// Создаем запись в БД
clientConfig := &model.ClientConfig{
UserId: userId,
Password: hashedPassword,
FileName: fileName,
FileHash: fileHash,
FileSize: fileInfo.Size(),
FilePath: filePath,
Description: description,
Status: model.COMMON_STATUS_ENABLE,
}
// Создаем базовую запись
if err := DB.Create(clientConfig).Error; err != nil {
// Удаляем файл в случае ошибки
os.Remove(filePath)
return nil, fmt.Errorf("failed to save client config to database: %v", err)
}
// Обновляем кастомные поля, если колонки существуют
if customConfigFile != "" {
// Проверяем наличие колонки перед обновлением
if DB.Migrator().HasColumn(&model.ClientConfig{}, "custom_config_file") {
DB.Model(clientConfig).Updates(map[string]interface{}{
"custom_config_file": customConfigFile,
"use_custom_config": true,
})
}
}
return clientConfig, nil
}
// generateTomlConfig генерирует TOML конфигурацию для RustDesk клиента
func (ccs *ClientConfigService) generateTomlConfig() string {
// Формируем TOML строку с настройками сервера
tomlStr := fmt.Sprintf(`rendezvous_server = "%s"
relay_server = "%s"
api_server = "%s"
key = "%s"
`, global.Config.Rustdesk.IdServer, global.Config.Rustdesk.RelayServer, global.Config.Rustdesk.ApiServer, global.Config.Rustdesk.Key)
return tomlStr
}
// calculateFileHash вычисляет MD5 хеш файла
func (ccs *ClientConfigService) calculateFileHash(filePath string) (string, error) {
file, err := os.Open(filePath)
if err != nil {
return "", err
}
defer file.Close()
hash := md5.New()
if _, err := io.Copy(hash, file); err != nil {
return "", err
}
return hex.EncodeToString(hash.Sum(nil)), nil
}
// GetByUserId получает список клиентов пользователя
func (ccs *ClientConfigService) GetByUserId(userId uint) []*model.ClientConfig {
var configs []*model.ClientConfig
DB.Where("user_id = ? AND status = ?", userId, model.COMMON_STATUS_ENABLE).
Order("created_at DESC").
Find(&configs)
return configs
}
// GetById получает клиент по ID
func (ccs *ClientConfigService) GetById(id uint) *model.ClientConfig {
config := &model.ClientConfig{}
DB.Where("id = ? AND status = ?", id, model.COMMON_STATUS_ENABLE).First(config)
return config
}
// List получает список клиентов с пагинацией
func (ccs *ClientConfigService) List(page, pageSize uint, scopes ...func(*gorm.DB) *gorm.DB) *model.ClientConfigList {
var configs []*model.ClientConfig
var total int64
query := DB.Model(&model.ClientConfig{}).Where("status = ?", model.COMMON_STATUS_ENABLE)
for _, scope := range scopes {
query = scope(query)
}
query.Count(&total)
query = Paginate(page, pageSize)(query)
query.Order("created_at DESC").Find(&configs)
return &model.ClientConfigList{
ClientConfigs: configs,
Pagination: model.Pagination{
Page: int64(page),
PageSize: int64(pageSize),
Total: total,
},
}
}
// Delete удаляет клиент (помечает как удаленный)
func (ccs *ClientConfigService) Delete(id, userId uint) error {
// Ищем запись без фильтра по status, чтобы можно было удалить даже если она уже помечена как удаленная
config := &model.ClientConfig{}
if err := DB.Where("id = ?", id).First(config).Error; err != nil {
return fmt.Errorf("client config not found")
}
// Проверяем, что клиент принадлежит пользователю
if config.UserId != userId {
return fmt.Errorf("access denied")
}
// Если уже удален, просто возвращаем успех
if config.Status == model.COMMON_STATUS_DISABLED {
return nil
}
// Помечаем как удаленный - обновляем только поле status, чтобы избежать проблем с отсутствующими колонками
if err := DB.Model(&model.ClientConfig{}).Where("id = ?", id).Update("status", model.COMMON_STATUS_DISABLED).Error; err != nil {
return err
}
// Удаляем файл
if config.FilePath != "" {
os.Remove(config.FilePath)
}
return nil
}
// VerifyPassword проверяет пароль клиента
func (ccs *ClientConfigService) VerifyPassword(configId uint, password string) bool {
config := ccs.GetById(configId)
if config.Id == 0 {
return false
}
ok, _, err := utils.VerifyPassword(config.Password, password)
return err == nil && ok
}