新增应用打包模块

This commit is contained in:
zyj
2025-11-24 11:45:42 +08:00
parent f393d2b1de
commit 51cfd5cf21
5 changed files with 740 additions and 51 deletions

340
services/pack_service.go Normal file
View File

@@ -0,0 +1,340 @@
package services
import (
"fmt"
"go-site-clone/utils"
"io"
"os"
"os/exec"
"path/filepath"
"strings"
"text/template"
)
// PackService 打包服务
type PackService struct{}
// PackConfig 打包配置
type PackConfig struct {
SitePath string // 网站文件夹路径
AppName string // 应用名称
Version string // 版本号
Author string // 作者
Description string // 描述
Platforms []string // 目标平台
Width int // 窗口宽度
Height int // 窗口高度
OutputDir string // 输出目录
}
// Wails项目模板
const mainGoTemplate = `package main
import (
"embed"
"log"
"github.com/wailsapp/wails/v3/pkg/application"
)
//go:embed all:frontend
var assets embed.FS
func main() {
app := application.New(application.Options{
Name: "{{.AppName}}",
Description: "{{.Description}}",
Assets: application.AssetOptions{
Handler: application.AssetFileServerFS(assets),
},
Mac: application.MacOptions{
ApplicationShouldTerminateAfterLastWindowClosed: true,
},
})
app.Window.NewWithOptions(application.WebviewWindowOptions{
Title: "{{.AppName}}",
Mac: application.MacWindow{
InvisibleTitleBarHeight: 50,
Backdrop: application.MacBackdropTranslucent,
TitleBar: application.MacTitleBarHiddenInset,
},
BackgroundColour: application.NewRGB(255, 255, 255),
URL: "/",
Width: {{.Width}},
Height: {{.Height}},
})
err := app.Run()
if err != nil {
log.Fatal(err)
}
}
`
const goModTemplate = `module {{.ModuleName}}
go 1.21
require github.com/wailsapp/wails/v3 v3.0.0-alpha.27
`
// CreateWailsProject 创建Wails项目结构
func (p *PackService) CreateWailsProject(config PackConfig, projectDir string) error {
// 1. 创建项目目录结构
dirs := []string{
projectDir,
filepath.Join(projectDir, "frontend"),
filepath.Join(projectDir, "build"),
}
for _, dir := range dirs {
if err := os.MkdirAll(dir, 0755); err != nil {
return fmt.Errorf("创建目录失败 %s: %v", dir, err)
}
}
// 1.1 复制项目根目录的 Taskfile.yml
currentDir, err := os.Getwd()
if err != nil {
return fmt.Errorf("获取当前目录失败: %v", err)
}
srcTaskfile := filepath.Join(currentDir, "Taskfile.yml")
dstTaskfile := filepath.Join(projectDir, "Taskfile.yml")
if err := p.copyFile(srcTaskfile, dstTaskfile); err != nil {
return fmt.Errorf("复制Taskfile.yml失败: %v", err)
}
// 1.2 复制整个 build 目录
srcBuildDir := filepath.Join(currentDir, "build")
dstBuildDir := filepath.Join(projectDir, "build")
if err := p.copyDirectory(srcBuildDir, dstBuildDir); err != nil {
return fmt.Errorf("复制build目录失败: %v", err)
}
// 2. 复制网站文件到frontend目录
frontendDir := filepath.Join(projectDir, "frontend")
if err := p.copyDirectory(config.SitePath, frontendDir); err != nil {
return fmt.Errorf("复制网站文件失败: %v", err)
}
// 2.1 创建一个空的 package.json避免构建系统报错
packageJSON := `{
"name": "static-site",
"version": "1.0.0",
"private": true,
"scripts": {
"build": "echo 'Static site, no build needed'",
"generate": "echo 'Static site, no build needed'"
}
}`
packageJSONPath := filepath.Join(frontendDir, "package.json")
if err := os.WriteFile(packageJSONPath, []byte(packageJSON), 0644); err != nil {
return fmt.Errorf("创建package.json失败: %v", err)
}
// 2.2 创建一个空的 dist 目录(如果构建系统需要)
distDir := filepath.Join(frontendDir, "dist")
if err := os.MkdirAll(distDir, 0755); err != nil {
return fmt.Errorf("创建dist目录失败: %v", err)
}
// 3. 生成main.go
mainGoPath := filepath.Join(projectDir, "main.go")
tmpl, err := template.New("main").Parse(mainGoTemplate)
if err != nil {
return fmt.Errorf("解析main.go模板失败: %v", err)
}
mainFile, err := os.Create(mainGoPath)
if err != nil {
return fmt.Errorf("创建main.go失败: %v", err)
}
defer mainFile.Close()
templateData := map[string]interface{}{
"AppName": config.AppName,
"Description": config.Description,
"Width": config.Width,
"Height": config.Height,
}
if err := tmpl.Execute(mainFile, templateData); err != nil {
return fmt.Errorf("生成main.go失败: %v", err)
}
// 4. 生成go.mod
goModPath := filepath.Join(projectDir, "go.mod")
modTmpl, err := template.New("gomod").Parse(goModTemplate)
if err != nil {
return fmt.Errorf("解析go.mod模板失败: %v", err)
}
modFile, err := os.Create(goModPath)
if err != nil {
return fmt.Errorf("创建go.mod失败: %v", err)
}
defer modFile.Close()
modData := map[string]interface{}{
"ModuleName": strings.ToLower(strings.ReplaceAll(config.AppName, " ", "-")),
}
if err := modTmpl.Execute(modFile, modData); err != nil {
return fmt.Errorf("生成go.mod失败: %v", err)
}
return nil
}
// BuildWailsApp 构建Wails应用
func (p *PackService) BuildWailsApp(projectDir string, outputDir string, platforms []string) error {
// 获取环境状态
envStatus, err := utils.GetEnvStatus()
if err != nil {
return fmt.Errorf("检查环境失败: %v", err)
}
// 确定使用的Go命令
goCmd := "go"
if envStatus.GoPath != "" {
goCmd = envStatus.GoPath
}
// 1. 执行 go mod tidy
tidyCmd := exec.Command(goCmd, "mod", "tidy")
tidyCmd.Dir = projectDir
if output, err := tidyCmd.CombinedOutput(); err != nil {
return fmt.Errorf("go mod tidy 失败: %v\n%s", err, output)
}
// 2. 直接使用 go build 编译,跳过复杂的构建流程
// 创建输出目录
if err := os.MkdirAll(outputDir, 0755); err != nil {
return fmt.Errorf("创建输出目录失败: %v", err)
}
// 确定输出文件名
appName := filepath.Base(projectDir)
outputFile := filepath.Join(outputDir, appName+".exe")
// 构建命令,使用生产环境标签
buildCmd := exec.Command(goCmd, "build",
"-tags", "production",
"-ldflags", "-w -s -H windowsgui",
"-o", outputFile,
".")
buildCmd.Dir = projectDir
// 设置环境变量
buildCmd.Env = append(os.Environ(),
"CGO_ENABLED=1",
"GOOS=windows",
)
output, err := buildCmd.CombinedOutput()
if err != nil {
return fmt.Errorf("go build 失败: %v\n%s", err, output)
}
return nil
}
// copyDirectory 递归复制目录
func (p *PackService) copyDirectory(src, dst string) error {
// 获取源目录信息
srcInfo, err := os.Stat(src)
if err != nil {
return err
}
// 创建目标目录
if err := os.MkdirAll(dst, srcInfo.Mode()); err != nil {
return err
}
// 读取源目录
entries, err := os.ReadDir(src)
if err != nil {
return err
}
for _, entry := range entries {
srcPath := filepath.Join(src, entry.Name())
dstPath := filepath.Join(dst, entry.Name())
if entry.IsDir() {
// 递归复制子目录
if err := p.copyDirectory(srcPath, dstPath); err != nil {
return err
}
} else {
// 复制文件
if err := p.copyFile(srcPath, dstPath); err != nil {
return err
}
}
}
return nil
}
// copyFile 复制单个文件
func (p *PackService) copyFile(src, dst string) error {
srcFile, err := os.Open(src)
if err != nil {
return err
}
defer srcFile.Close()
dstFile, err := os.Create(dst)
if err != nil {
return err
}
defer dstFile.Close()
if _, err := io.Copy(dstFile, srcFile); err != nil {
return err
}
// 复制文件权限
srcInfo, err := os.Stat(src)
if err != nil {
return err
}
return os.Chmod(dst, srcInfo.Mode())
}
// copyBuildOutput 复制构建输出
func (p *PackService) copyBuildOutput(buildDir, outputDir string) error {
if err := os.MkdirAll(outputDir, 0755); err != nil {
return err
}
// 检查 buildDir 是否存在
if _, err := os.Stat(buildDir); os.IsNotExist(err) {
return fmt.Errorf("构建目录不存在: %s", buildDir)
}
entries, err := os.ReadDir(buildDir)
if err != nil {
return err
}
for _, entry := range entries {
srcPath := filepath.Join(buildDir, entry.Name())
dstPath := filepath.Join(outputDir, entry.Name())
if entry.IsDir() {
if err := p.copyDirectory(srcPath, dstPath); err != nil {
return err
}
} else {
if err := p.copyFile(srcPath, dstPath); err != nil {
return err
}
}
}
return nil
}