Files
RUSTDESK-AP-SERVER-SUNLIX/http/controller/admin/clientBuild.go

317 lines
9.5 KiB
Go

package admin
import (
"encoding/base64"
"encoding/json"
"os"
"path/filepath"
"strconv"
"strings"
"github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/http/request/admin"
"github.com/lejianwen/rustdesk-api/v2/http/response"
"github.com/lejianwen/rustdesk-api/v2/service"
"gorm.io/gorm"
)
type ClientBuild struct {
}
// Generate запускает генерацию/компиляцию клиента
// @Tags 客户端编译
// @Summary 生成客户端
// @Description 启动客户端编译任务
// @Accept json
// @Produce json
// @Param body body admin.ClientBuildGenerateForm true "客户端配置"
// @Success 200 {object} response.Response{data=model.ClientBuild}
// @Failure 500 {object} response.Response
// @Router /admin/client_build/generate [post]
// @Security token
func (ct *ClientBuild) Generate(c *gin.Context) {
f := &admin.ClientBuildGenerateForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
errList := global.Validator.ValidStruct(c, f)
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
u := service.AllService.UserService.CurUser(c)
if u.Id == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "UserNotFound"))
return
}
// Парсим дополнительные настройки из config
var customConfig map[string]string
if f.Config != "" {
if err := json.Unmarshal([]byte(f.Config), &customConfig); err != nil {
customConfig = make(map[string]string)
}
} else {
customConfig = make(map[string]string)
}
// Используем настройки сервера из конфигурации, если не указаны (только если не используется кастомный конфиг)
serverIP := f.ServerIP
if serverIP == "" && !f.UseCustomConfig {
serverIP = global.Config.Rustdesk.IdServer
}
apiServer := f.ApiServer
if apiServer == "" && !f.UseCustomConfig {
apiServer = global.Config.Rustdesk.ApiServer
}
key := f.Key
if key == "" && !f.UseCustomConfig {
key = global.Config.Rustdesk.Key
}
// Обрабатываем кастомный конфигурационный файл
// Принимаем любой конфигурационный файл без проверки сертификатов/подписей
customConfigFile := f.CustomConfigFile
if f.UseCustomConfig && customConfigFile != "" {
// Пытаемся декодировать base64, если не получается - используем как есть
decoded, err := base64.StdEncoding.DecodeString(customConfigFile)
if err == nil {
customConfigFile = string(decoded)
}
// Если декодирование не удалось, используем исходную строку как текст конфига
}
buildConfig := &service.BuildConfig{
Platform: f.Platform,
Version: f.Version,
AppName: f.AppName,
FileName: f.FileName,
ServerIP: serverIP,
ApiServer: apiServer,
Key: key,
Custom: customConfig,
CustomConfigFile: customConfigFile,
UseCustomConfig: f.UseCustomConfig,
}
build, err := service.AllService.ClientBuildService.CreateBuild(u.Id, buildConfig)
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, build)
}
// List получает список задач компиляции
// @Tags 客户端编译
// @Summary 编译任务列表
// @Description 获取当前用户的编译任务列表
// @Accept json
// @Produce json
// @Param page query int false "页码"
// @Param page_size query int false "页大小"
// @Param platform query string false "平台"
// @Param status query string false "状态"
// @Success 200 {object} response.Response{data=model.ClientBuildList}
// @Failure 500 {object} response.Response
// @Router /admin/client_build/list [get]
// @Security token
func (ct *ClientBuild) List(c *gin.Context) {
query := &admin.ClientBuildQuery{}
if err := c.ShouldBindQuery(query); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
u := service.AllService.UserService.CurUser(c)
if u.Id == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "UserNotFound"))
return
}
res := service.AllService.ClientBuildService.List(uint(query.Page), uint(query.PageSize), func(tx *gorm.DB) *gorm.DB {
tx = tx.Where("user_id = ?", u.Id)
if query.Platform != "" {
tx = tx.Where("platform = ?", query.Platform)
}
if query.Status != "" {
tx = tx.Where("status = ?", query.Status)
}
return tx
})
response.Success(c, res)
}
// Status получает статус задачи компиляции
// @Tags 客户端编译
// @Summary 编译状态
// @Description 获取编译任务的状态
// @Accept json
// @Produce json
// @Param uuid path string true "Build UUID"
// @Success 200 {object} response.Response{data=admin.ClientBuildStatusResponse}
// @Failure 500 {object} response.Response
// @Router /admin/client_build/status/{uuid} [get]
// @Security token
func (ct *ClientBuild) Status(c *gin.Context) {
uuid := c.Param("uuid")
if uuid == "" {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
u := service.AllService.UserService.CurUser(c)
if u.Id == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "UserNotFound"))
return
}
build := service.AllService.ClientBuildService.GetByUuid(uuid)
if build.Id == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
// Проверяем, что задача принадлежит пользователю
if build.UserId != u.Id {
response.Fail(c, 101, response.TranslateMsg(c, "AccessDenied"))
return
}
// Вычисляем прогресс
progress := 0
if build.Status == "pending" {
progress = 10
} else if build.Status == "building" {
progress = 50
} else if build.Status == "success" {
progress = 100
} else if build.Status == "failed" {
progress = 0
}
statusResp := &admin.ClientBuildStatusResponse{
BuildUuid: build.BuildUuid,
Status: build.Status,
Progress: progress,
Log: build.BuildLog,
ErrorMsg: build.ErrorMsg,
}
// Если сборка успешна, добавляем URL для скачивания
if build.Status == "success" && build.FilePath != "" {
statusResp.FileUrl = "/api/admin/client_build/download/" + build.BuildUuid
}
response.Success(c, statusResp)
}
// Download скачивает скомпилированный клиент
// @Tags 客户端编译
// @Summary 下载客户端
// @Description 下载编译完成的客户端文件
// @Accept json
// @Produce application/octet-stream
// @Param uuid path string true "Build UUID"
// @Success 200 {file} file
// @Failure 500 {object} response.Response
// @Router /admin/client_build/download/{uuid} [get]
// @Security token
func (ct *ClientBuild) Download(c *gin.Context) {
uuid := c.Param("uuid")
if uuid == "" {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
u := service.AllService.UserService.CurUser(c)
if u.Id == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "UserNotFound"))
return
}
build := service.AllService.ClientBuildService.GetByUuid(uuid)
if build.Id == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
// Проверяем, что задача принадлежит пользователю
if build.UserId != u.Id {
response.Fail(c, 101, response.TranslateMsg(c, "AccessDenied"))
return
}
// Проверяем, что сборка успешна
if build.Status != "success" {
response.Fail(c, 101, "Build is not completed yet")
return
}
// Проверяем существование файла
if _, err := os.Stat(build.FilePath); os.IsNotExist(err) {
response.Fail(c, 101, response.TranslateMsg(c, "FileNotFound"))
return
}
// Отправляем файл
// Определяем Content-Type в зависимости от расширения файла
contentType := "application/octet-stream"
fileExt := strings.ToLower(filepath.Ext(build.FilePath))
switch fileExt {
case ".exe":
contentType = "application/x-msdownload"
case ".deb":
contentType = "application/vnd.debian.binary-package"
case ".dmg":
contentType = "application/x-apple-diskimage"
case ".apk":
contentType = "application/vnd.android.package-archive"
case ".zip":
contentType = "application/zip"
}
c.Header("Content-Disposition", "attachment; filename="+build.FileName)
c.Header("Content-Type", contentType)
c.File(build.FilePath)
}
// Delete удаляет задачу компиляции
// @Tags 客户端编译
// @Summary 删除编译任务
// @Description 删除编译任务及其文件
// @Accept json
// @Produce json
// @Param id path int true "Build ID"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/client_build/delete/{id} [post]
// @Security token
func (ct *ClientBuild) Delete(c *gin.Context) {
id := c.Param("id")
idUint, err := strconv.ParseUint(id, 10, 32)
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
u := service.AllService.UserService.CurUser(c)
if u.Id == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "UserNotFound"))
return
}
err = service.AllService.ClientBuildService.Delete(uint(idUint), u.Id)
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, nil)
}