go-GUI开发:fyne解决中文乱码+注册windows服务

news2024/9/19 10:39:10

go-GUI框架:fyne教程及解决中文乱码等常见bug

1 fyne教程

  • fyne教程地址:
  • https://www.topgoer.cn/docs/goday/goday-1crdp17nj4v6p
  • https://pkg.go.dev/fyne.io/fyne/v2#section-readme

1.1 介绍

  1. 简单易用,fyne提供了简单直观的API,可以快速学习并上手进行开发
  2. 跨平台支持,fyne提供了多个平台的支持,包括:Windows、MacOs、Linux等
  3. 响应式布局,提供了灵活的布局管理器,我们可以根据需要动态调整页面元素的位置和大小
  4. 丰富的组件库,fyne内置许多常用且美观的组件库,大大降低了一个GUI的开发成本
  5. 可扩展性高,允许我们自定义渲染器、主题和样式,最大程度上的对页面进行DIY
  6. 事件处理强大,提供了丰富的事件处理机制和系统托盘,我们可以通过监听用户输入、按钮点击等事件来实现与用户的交互
  7. 社区活跃和文档丰富,fyne拥有一个活跃的社区和丰富的文档教程等,如果遇到bug可以在一定程度上寻求解决办法

注意:fyne不支持go1.12以下的go版本,同时不支持32位的windows XP系统

1.2 使用(项目实战:GUI客户端+托盘)

下面将用fyne2实现系统托盘,并且点击托盘不同菜单展示不同页面

项目目录结构:


①main.go

项目入口

package main

import (
	"github.com/flopp/go-findfont"
	"os"
	"strings"
	"ziyi.com/ziyi-guard/ui"
	"ziyi.com/ziyi-guard/ui/service"
)

func init() {
	//设置中文字体:解决中文乱码问题
	fontPaths := findfont.List()
	for _, path := range fontPaths {
		if strings.Contains(path, "msyh.ttf") || strings.Contains(path, "simhei.ttf") || strings.Contains(path, "simsun.ttc") || strings.Contains(path, "simkai.ttf") {
			os.Setenv("FYNE_FONT", path)
			break
		}
	}
}

func main() {
	args := os.Args
	if len(args) >= 2 {
		arg := args[1]
		//根据参数做对应操作
		service.SelectXGuard(arg)
	} else {
		app := ui.NewGuardApp()
		app.W.ShowAndRun()
	}

}

②sys_service.go

注册windows服务

package service

import (
	"fmt"
	"github.com/kardianos/service"
	"log"
	"os"
	"time"
	"ziyi.com/ziyi-guard/consts"
)

var (
	// 创建一个ServiceConfig对象,用于描述服务的配置信息
	svcConfig = &service.Config{
		Name:        consts.ServiceName,
		DisplayName: consts.ServiceDisplayName,
		Description: consts.ServiceDescription,
		Arguments:   consts.Arguments,
		Option:      consts.Options,
	}

	// 创建一个Program对象
	prog = &Program{exit: make(chan struct{})}
	s    service.Service
)

func init() {
	// 将Program对象与ServiceConfig对象绑定,并创建一个新的Service对象
	svc, err := service.New(prog, svcConfig)
	if err != nil {
		log.Fatalf("init service failed...err=%v", err)
	}
	s = svc
}

// Program 结构体定义了实现Service接口所需的方法
type Program struct {
	exit chan struct{}
}

// Start 是在service.Start方法调用时被自动调用的方法
// 在启动服务时执行具体的业务代码
func (p *Program) Start(s service.Service) error {
	go p.run()
	return nil
}

// Stop 是在service.Stop方法调用时被自动调用的方法
func (p *Program) Stop(s service.Service) error {
	close(p.exit)
	return nil
}

func SelectXGuard(arg string) {
	// 如果命令行参数为install、start、stop或restart,则执行对应的操作
	// 如果没有命令行参数,则输出命令行帮助信息
	switch arg {
	case "install":
		err := s.Install()
		if err != nil {
			log.Fatal(err)
		}
		log.Printf("Service %s installed.", s.String())
	case "start":
		err := s.Start()
		if err != nil {
			log.Fatal(err)
		}
		log.Printf("Service %s started.", s.String())
	case "stop":
		err := s.Stop()
		if err != nil {
			log.Fatal(err)
		}
		log.Printf("Service %s stopped.", s.String())
	case "uninstall":
		err := s.Uninstall()
		if err != nil {
			log.Fatal(err)
		}
		log.Printf("Service %s uninstall.", s.String())
	case "run":
		err := s.Run()
		if err != nil {
			log.Fatal(err)
		}
		log.Printf("Service %s run.", s.String())
	case "restart":
		err := s.Stop()
		if err != nil {
			log.Fatal(err)
		}
		log.Printf("Service %s stopped.", s.String())
		err = s.Start()
		if err != nil {
			log.Fatal(err)
		}
		log.Printf("Service %s started.", s.String())
	default:
		log.Printf("Usage: %s install|uninstall|start|stop|restart", os.Args[0])
	}
}

func (p *Program) run() {
	file, err := os.OpenFile("E:\\Go\\GoPro\\src\\go_code\\work\\ziyi-guard\\log.txt", os.O_WRONLY|os.O_RDONLY, os.ModePerm)
	defer file.Close()
	if err != nil {
		log.Println("open file err=", err)
		return
	}
	for {
		time.Sleep(time.Second * 5)
		file.WriteString(fmt.Sprintf(time.Now().String()) + " hello\n")
	}
}

③data_check.go

数据格式校验等

package ui

import (
	"errors"
	"fyne.io/fyne/v2"
	"fyne.io/fyne/v2/dialog"
	"fyne.io/fyne/v2/widget"
	"github.com/aobco/log"
	"os/exec"
	"regexp"
	"strings"
	"time"
)

/*
数据合法性校验、URL连通性校验等
*/
var (
	isConnected = make(chan bool, 1)
	urlRegex    = `^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}):(\d{1,5})$`
)

// 数据合法性校验
func DataCheck(w fyne.Window, data *GuardData) error {
	url := data.url
	name := data.name
	tags := data.tags
	//校验 IP:Port 格式
	r := regexp.MustCompile(urlRegex)
	ok := r.MatchString(url)
	if !ok || url == "" {
		dialog.NewError(errors.New("url格式有误,请按照:【192.168.145.13:7777】格式填写"),
			w,
		).Show()
		return errors.New("数据格式有误")
	}
	if name == "" {
		dialog.NewError(errors.New("name格式有误,输入内容不能为空"),
			w,
		).Show()
		return errors.New("数据格式有误")
	}
	if tags == "" {
		dialog.NewError(errors.New("tags格式有误,请按照:【--port=8082, --url=www.baidu.com 】格式填写\n(参数之间使用逗号分割)"),
			w,
		).Show()
		return errors.New("数据格式有误")
	}
	return nil
}

// 通过执行 ping 命令检测指定 IP 是否可以 ping 通
func PingIp(ip string, w fyne.Window) bool {
	start := time.Now().Second()
	cmd := exec.Command("ping", ip)
	go func() {
		log.Infof("检测iP....%v", ip)
		w.SetContent(widget.NewLabel("checking ... URL....please don't close the page, otherwise the application will crash\n" +
			"检测URL连通性中,请不要关闭该页面,否则将引起页面崩溃..."))
		w.Show()
	}()
	err := cmd.Run()
	end := time.Now().Second()
	log.Infof("check ip cost time :%v", end-start)
	if err != nil {
		log.Infof("check ip , connect the target fail, err=%s", err)
		isConnected <- false
		return false
	}
	isConnected <- true
	return true
}

func ConnectTarget(url string, w fyne.Window) error {
	log.Infof("检测ziyi-guard配置目标 URl连通性,url:%v", url)
	ip := strings.Split(url, ":")[0]
	isConnected = make(chan bool, 1)
	PingIp(ip, w)
	select {
	case flag := <-isConnected:
		if flag {
			close(isConnected)
		} else {
			err := errors.New("connect the target failed error")
			return err
		}
	}
	return nil
}

④func_file.go

对文件进行操作

package ui

import (
	"bufio"
	"fmt"
	"fyne.io/fyne/v2"
	"fyne.io/fyne/v2/container"
	"fyne.io/fyne/v2/widget"
	"github.com/aobco/log"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"ziyi.com/ziyi-guard/consts"
	"ziyi.com/ziyi-guard/ui/config"
)

/*
对guardData.cong文件操作
*/
func init() {
	wd, _ := os.Getwd()
	filename = filepath.Dir(wd) + consts.XGuardConfRelativePath
}

var (
	filename    string
	guardConfig = config.NewDefaultGuardConfig()
)

// 从配置文件读取信息
func ReadConfFile() (string, error) {
	file, err := os.Open(filename)
	if err != nil {
		log.Infof("打开ziyi-guard配置文件失败 filename=%s error=%s", filename, err)
		return "", err
	}
	defer file.Close()
	if _, err := os.Stat(filename); err != nil {
		if os.IsNotExist(err) {
			log.Infof("xguard配置文件不存在,xguard.conf...")
			if _, err := os.Create(filename); err != nil {
				log.Infof("新建xguard.conf配置文件失败, error=%s", err)
				return "", err
			}
		}
	}

	reader := bufio.NewReader(file)
	tmpSlice := make([]string, 3, 3)
	for {
		str, err := reader.ReadString('\n')
		if str != "" {
			str = strings.Replace(str, "\n", "", -1)
			str = strings.TrimSpace(str)
		}
		tmpSlice = append(tmpSlice, str)
		if err == io.EOF {
			break
		}
	}
	guardResData := ""
	for _, v := range tmpSlice {
		if v != "" {
			guardResData += v
			guardResData += separator
		}
	}
	return guardResData, nil
}

// 保存配置信息到文件[write]
func WriteConfFile(data string) error {
	file, err := os.Create(filename)
	if err != nil {
		return err
	}
	defer file.Close()
	_, err = fmt.Fprint(file, data)
	if err != nil {
		return err
	}
	log.Infof("保存xguard配置文件信息:%s", data)
	return nil
}

// 从配置文件读取Agent信息
func ReadAllAgentInfo(myWindow fyne.Window) []string {
	//选项个数根据配置文件个数来
	dirPath, _ := os.ReadDir(guardConfig.AgentDirPath)
	dirCount := len(dirPath)
	options := make([]string, dirCount)
	index := 1
	for {
		if index <= dirCount {
			options = append(options, fmt.Sprintf("agent%v", index))
			index++
		} else {
			break
		}
	}
	index = 0
	choices := make([]string, dirCount)
	for _, v := range options {
		if v != "" {
			choices[index] = v
			index++
		}
	}

	log.Info("Agent下拉框选项:%v", choices)
	// 创建下拉框
	selectEntry := widget.NewSelectEntry(choices)

	selectEntry.PlaceHolder = "please choose an agent"
	// 创建左侧固定区域
	left := container.NewVBox(
		widget.NewLabel("Select an option:"),
		selectEntry,
	)
	// 创建右侧区域
	right := container.NewVBox(
		widget.NewLabel("agent basic information"),
		widget.NewLabel(""),
	)
	// 创建水平分割容器,并设置分割比例
	split := container.NewHSplit(left, right)
	split.SetOffset(0.25)
	// 设置右侧标签的文本为下拉框的当前值
	selectEntry.OnChanged = func(s string) {
		fileName := guardConfig.AgentDirPath + "\\" + s + ".conf"
		fmt.Println(fileName)
		data, err := readConfig(fileName)
		if err != nil {
			//dialog.NewInformation("info", err.Error(), myWindow).Show()
			fmt.Println("err=", err)
		}
		right.Objects[1].(*widget.Label).SetText(data)
	}
	myWindow.SetContent(split)
	myWindow.Resize(fyne.Size{Height: 500, Width: 800})
	return choices
}

func readConfig(path string) (string, error) {
	file, err := os.Open(path)
	if err != nil {
		return "", err
	}
	defer file.Close()

	contentBytes, err := ioutil.ReadAll(file)
	if err != nil {
		return "", err
	}

	content := string(contentBytes)
	return content, nil
}

⑤guard_desk.go

构建系统托盘

package ui

import (
	"fyne.io/fyne/v2"
	"fyne.io/fyne/v2/driver/desktop"
	"github.com/aobco/log"
	"io/ioutil"
	"os"
	"path/filepath"
	"ziyi.com/ziyi-guard/consts"
)

/*
ziyi-guard系统托盘图标构建
*/
func init() {
	wd, _ := os.Getwd()
	iconPath = filepath.Dir(wd) + consts.IconRelativePath
}

var (
	iconPath string
)

type GuardDesk struct {
	trayIcon fyne.Resource
	trayMenu fyne.Menu
}

func InitGuardDesk(guardApp GuardApp) error {
	if desk, ok := guardApp.A.(desktop.App); ok {
		//获取托盘与图标
		menu := NewGuardTrayMenu(guardApp.W)
		iconData, err := TransIconToByte()
		if err != nil {
			log.Infof("获取弹框图标失败,err=", err)
			return err
		}
		desk.SetSystemTrayMenu(menu)
		desk.SetSystemTrayIcon(fyne.NewStaticResource("ziyi-guard", iconData))
	}
	return nil
}

// 将.ico转换为字节数组
func TransIconToByte() ([]byte, error) {
	file, err := os.Open(iconPath)
	if err != nil {
		log.Infof("转换icon失败 open file err=%s", err)
		return nil, err
	}
	bytes, err := ioutil.ReadAll(file)
	if err != nil {
		log.Infof("转换icon失败 read icon err=%s", err)
		return nil, err
	}
	return bytes, nil
}

⑥tray_menu.go

托盘对应菜单点击事件

package ui

import (
	"fyne.io/fyne/v2"
	"fyne.io/fyne/v2/container"
	"fyne.io/fyne/v2/dialog"
	"fyne.io/fyne/v2/widget"
	"github.com/aobco/log"
	"net"
	"strings"
)

type GuardTrayMenu struct {
}

func NewGuardTrayMenu(w fyne.Window) *fyne.Menu {
	m := fyne.NewMenu("ziyi-guard",
		fyne.NewMenuItem("配置ziyi-guard", func() {
			w.Show()
		}),
		fyne.NewMenuItem("查看配置信息", func() {
			w.SetTitle("ziyi-guard信息查看")
			MakeGuardView(w)
			w.Show()
		}),
		fyne.NewMenuItem("获取本地IP", func() {
			w.SetTitle("本地IP地址")
			MakeIpView(w)
			w.Show()
		}),
		fyne.NewMenuItem("启动服务", func() {
			StartService(w)
		}),
		fyne.NewMenuItem("停止服务", func() {
			StopService(w)
		}),
		fyne.NewMenuItemSeparator(),
		fyne.NewMenuItem("Agent信息", func() {
			//查看Agent信息
			ShowAgentInfo(w)
			w.Show()
		}),
	)

	return m
}

// 查看Guard配置信息
func MakeGuardView(w fyne.Window) {
	data, err := ReadConfFile()
	if err != nil {
		log.Infof("读取ziyi-guard配置文件失败 err=", err)
	}
	w.SetTitle("ziyi-guard配置信息")
	split := strings.Split(data, "####")
	str := "暂无配置信息"
	if len(split) != 1 {
		form := widget.NewForm(
			widget.NewFormItem("URL:", widget.NewLabel(split[0])),
			widget.NewFormItem("Name:", widget.NewLabel(split[1])),
			widget.NewFormItem("Tags:", widget.NewLabel(split[2])),
		)
		content := container.NewVBox(form)
		w.SetContent(content)
	} else {
		content := widget.NewLabel(str)
		w.SetContent(content)
	}
}

// 查看IP页面
func MakeIpView(w fyne.Window) {
	ipAddress := GetLocalIp()
	content := widget.NewLabel("本地IP地址:" + ipAddress)
	w.SetContent(content)
}

// 获取本地IP地址
func GetLocalIp() string {
	interfaces, err := net.Interfaces()
	if err != nil {
		log.Info(err)
		return ""
	}

	for _, iface := range interfaces {
		if strings.Contains(iface.Name, "Win Adapter") {
			addrs, err := iface.Addrs()
			if err != nil {
				log.Info(err)
				continue
			}
			for _, addr := range addrs {
				ipnet, ok := addr.(*net.IPNet)
				if ok && !ipnet.IP.IsLoopback() {
					if ipnet.IP.To4() != nil {
						ip := ipnet.IP.String()
						log.Infof("获取本地IP成功:%s", ip)
						if !strings.HasPrefix(ip, "169.254") {
							return ip
						}
					}
				}
			}
		}
	}

	return ""
}

// 启动服务
func StartService(w fyne.Window) {
	//TODO 接收服务启动成功消息
	w.Show()
	dialog.NewInformation("信息", "启动服务成功", w).Show()
	//复原mainMenu
	MakeMainUI(w, GuardData{
		url:  "",
		name: "",
		tags: "",
	})
}

// 停止服务
func StopService(w fyne.Window) {
	//TODO 接收服务停止消息
	w.Show()
	dialog.NewInformation("信息", "停止服务成功", w).Show()
	MakeMainUI(w, GuardData{
		url:  "",
		name: "",
		tags: "",
	})
}

// 构建页面【查看所有Agent信息】
func ShowAgentInfo(w fyne.Window) {
	//读取agent配置信息
	ReadAllAgentInfo(w)
}

⑦main_window.go

UI系统主页面

package ui

import (
	"errors"
	"fyne.io/fyne/v2"
	"fyne.io/fyne/v2/container"
	"fyne.io/fyne/v2/dialog"
	"fyne.io/fyne/v2/widget"
	"github.com/aobco/log"
	"net"
	"strings"
)

/*
ziyi-guard配置主页面
*/
type GuardData struct {
	url  string
	name string
	tags string
}

var (
	separator = "####"
	//ipRegex   = `((2(5[0-5]|[0-4]\\d))|[0-1]?\\d{1,2})(\\.((2(5[0-5]|[0-4]\\d))|[0-1]?\\d{1,2})){3}`
)

// MakeMainUI
func MakeMainUI(myWindow fyne.Window, guardData GuardData) {
	myWindow.SetTitle("ziyi-guard主页面")
	// 创建输入选项卡
	urlEntry := widget.NewEntry()
	nameEntry := widget.NewEntry()
	tagEntry := widget.NewEntry()
	//数据回显
	if guardData.url != "" {
		urlEntry.Text = guardData.url
	}
	if guardData.name != "" {
		nameEntry.Text = guardData.name
	}
	if guardData.tags != "" {
		tagEntry.Text = guardData.tags
	}
	// 创建
	data, err := ReadConfFile()
	if err != nil {
		log.Infof("读取ziyi-guard配置文件失败:%s", err)
	}
	split := strings.Split(data, separator)
	log.Infof("读取ziyi-guard配置文件成功:%s", split)
	str := "暂无配置信息"
	var showTab *container.TabItem
	tmpUrl := ""
	if len(split) != 1 {
		form := widget.NewForm(
			widget.NewFormItem("URL:", widget.NewLabel(split[0])),
			widget.NewFormItem("Name:", widget.NewLabel(split[1])),
			widget.NewFormItem("Tags:", widget.NewLabel(split[2])),
		)
		//配置页面数据回显
		urlEntry.Text = split[0]
		nameEntry.Text = split[1]
		tagEntry.Text = split[2]
		tmpUrl = split[0]
		content := container.NewVBox(form)
		showTab = container.NewTabItem("查看配置", content)
	} else {
		urlEntry.SetPlaceHolder("例:192.168.145.13:7777")
		nameEntry.SetPlaceHolder("例:ziyi-guard守护")
		tagEntry.SetPlaceHolder("例:--port=8082, -C=/usr/local/guard")
		content := widget.NewLabel(str)
		showTab = container.NewTabItem("查看配置", content)
	}
	//监听点击事件
	submitButton := widget.NewButton("提交", func() {
		url := urlEntry.Text
		name := nameEntry.Text
		tags := tagEntry.Text
		log.Infof("用户提交ziyi-guard配置信息 url:%s,name:%s,tags:%s\n", url, name, tags)
		//更新showTab内容
		url = urlEntry.Text
		name = nameEntry.Text
		tags = tagEntry.Text
		//tmpUrl用于connectBtn对连通性进行检测
		if tmpUrl == "" || url != "" {
			tmpUrl = url
		}
		//数据合法性校验
		err = DataCheck(myWindow, &GuardData{
			url:  url,
			name: name,
			tags: tags,
		})
		if err != nil {
			//清空输入框[用户体验:不清空]
			MakeMainUI(myWindow, GuardData{
				url:  url,
				name: name,
				tags: tags,
			})
			return
		}
		form := widget.NewForm(
			widget.NewFormItem("URL:", widget.NewLabel(url)),
			widget.NewFormItem("Name:", widget.NewLabel(name)),
			widget.NewFormItem("Tags:", widget.NewLabel(tags)),
		)
		showTab.Content = container.NewVBox(form)
		//保存数据到文件
		WriteConfFile(url + "\n" + name + "\n" + tags + "\n")

		dialog.NewInformation("信息",
			"更新数据成功",
			myWindow,
		).Show()
		MakeMainUI(myWindow, GuardData{
			url:  url,
			name: name,
			tags: tags,
		})
		//清空内容
		urlEntry.SetText("")
		nameEntry.SetText("")
		tagEntry.SetText("")

	})
	submitButton.Importance = widget.HighImportance
	//检测URL连通性
	connectBtn := widget.NewButton("检测URL连通性", func() {
		text := urlEntry.Text
		if text != "" {
			tmpUrl = text
			tmpIp := strings.Split(text, ":")[0]
			//正则匹配
			address := net.ParseIP(tmpIp)
			if address == nil {
				log.Infof("tmpIp:%v", tmpIp)
				dialog.NewError(errors.New("请输入正确的IP格式"), myWindow).Show()
				return
			}
		}
		err = ConnectTarget(tmpUrl, myWindow)
		if err != nil {
			dialog.NewError(errors.New("连接到目标端失败,请更换URL"), myWindow).Show()
		} else {
			dialog.NewInformation("成功", "连接到目标端成功", myWindow).Show()
		}
		MakeMainUI(myWindow, GuardData{
			url:  urlEntry.Text,
			name: nameEntry.Text,
			tags: tagEntry.Text,
		})
	})

	submitTab := container.NewTabItem("配置", container.NewVBox(
		widget.NewLabel("URL:"),
		urlEntry,
		widget.NewLabel("Name:"),
		nameEntry,
		widget.NewLabel("tags:"),
		tagEntry,
		connectBtn,
		submitButton,
	))
	// 创建选项卡容器
	tabs := container.NewAppTabs(submitTab, showTab)

	// 将选项卡容器添加到窗口中并显示
	myWindow.SetContent(tabs)
	myWindow.Resize(fyne.NewSize(float32(500.0), float32(500.0)))
}

⑧guard_app.go

构建fyne2的app

package ui

import (
	"fyne.io/fyne/v2"
	"fyne.io/fyne/v2/app"
	"fyne.io/fyne/v2/driver/desktop"
	"fyne.io/fyne/v2/theme"
	"github.com/aobco/log"
)

/*
*
ziyi-guard的UI界面构建
*/
type GuardApp struct {
	A fyne.App
	W fyne.Window
}

func NewGuardApp() *GuardApp {
	myApp := &GuardApp{}
	a := app.New()
	//主题设置
	a.Settings().SetTheme(theme.DarkTheme())
	w := a.NewWindow("配置信息")
	myApp.W = w
	iconData, err := TransIconToByte()
	if err != nil {
		log.Infof("获取弹框图标失败, err=%s", err)
	}
	//弹框图标、位置、大小
	icon := fyne.NewStaticResource("ziyi-guard", iconData)
	w.SetIcon(icon)
	w.CenterOnScreen()
	w.Resize(fyne.NewSize(float32(500.0), float32(500.0)))
	//系统托盘
	systemTrayMenu := NewGuardTrayMenu(w)
	desk := a.(desktop.App)
	desk.SetSystemTrayMenu(systemTrayMenu)
	desk.SetSystemTrayIcon(icon)
	//设置拦截器
	w.SetCloseIntercept(func() {
		MakeMainUI(w, GuardData{
			url:  "",
			name: "",
			tags: "",
		})
	})
	//主页面
	MakeMainUI(w, GuardData{
		url:  "",
		name: "",
		tags: "",
	})
	//设置拦截
	w.SetCloseIntercept(func() {
		w.Hide()
		MakeMainUI(w, GuardData{
			url:  "",
			name: "",
			tags: "",
		})
	})
	return &GuardApp{
		A: a,
		W: w,
	}
}

bug

①解决中文乱码问题(编译打包可用)

  1. 导入库
import "github.com/flopp/go-findfont"
  1. 添加初始化代码
func init() {
	//设置中文字体
	fontPaths := findfont.List()
	for _, path := range fontPaths {
		if strings.Contains(path, "msyh.ttf") || strings.Contains(path, "simhei.ttf") || strings.Contains(path, "simsun.ttc") || strings.Contains(path, "simkai.ttf") {
			os.Setenv("FYNE_FONT", path)
			break
		}
	}
}

2 注册windows服务

package main

import (
	"fmt"
	"github.com/kardianos/service"
	"os"
)


func main() {
	srvConfig := &service.Config{
		Name:        "MyGoService",
		DisplayName: "MyGoService服务",
		Description: "this is a service about go",
	}
	prg := &program{}
	s, err := service.New(prg, srvConfig)
	if err != nil {
		fmt.Println(err)
	}
	if len(os.Args) > 1 {
		serviceAction := os.Args[1]
		switch serviceAction {
		case "install":
			err := s.Install()
			if err != nil {
				fmt.Println("安装服务失败: ", err.Error())
			} else {
				fmt.Println("安装服务成功")
			}
			return
		case "uninstall":
			err := s.Uninstall()
			if err != nil {
				fmt.Println("卸载服务失败: ", err.Error())
			} else {
				fmt.Println("卸载服务成功")
			}
			return
		case "start":
			err := s.Start()
			if err != nil {
				fmt.Println("运行服务失败: ", err.Error())
			} else {
				fmt.Println("运行服务成功")
			}
			return
		case "stop":
			err := s.Stop()
			if err != nil {
				fmt.Println("停止服务失败: ", err.Error())
			} else {
				fmt.Println("停止服务成功")
			}
			return
		}
	}

	err = s.Run()
	if err != nil {
		fmt.Println(err)
	}
}

type program struct{}

func (p *program) Start(s service.Service) error {
	fmt.Println("服务运行...")
	go p.run()
	return nil
}
func (p *program) run() {
	// 具体的服务实现
}
func (p *program) Stop(s service.Service) error {
	return nil
}

添加上启动参数:

package main

import (
	"fmt"
	"github.com/kardianos/service"
	"log"
	"os"
	"time"
)

// Program 结构体定义了实现Service接口所需的方法
type Program struct {
	exit chan struct{}
}

// Start 是在service.Start方法调用时被自动调用的方法
// 在启动服务时执行具体的业务代码
func (p *Program) Start(s service.Service) error {
	go p.run()
	return nil
}

// Stop 是在service.Stop方法调用时被自动调用的方法
func (p *Program) Stop(s service.Service) error {
	close(p.exit)
	return nil
}

func main() {
	// 创建一个ServiceConfig对象,用于描述服务的配置信息
	svcConfig := &service.Config{
		Name:        "MyService",
		DisplayName: "MyService",
		Description: "This is a service for Guard.",
		Arguments:   []string{"run"},
		Option: service.KeyValue{
			"StartType":              "automatic",
			"OnFailure":              service.OnFailureRestart,
			"OnFailureDelayDuration": "1m",
			"OnFailureResetPeriod":   10,
		},
	}

	// 创建一个Program对象
	prog := &Program{exit: make(chan struct{})}

	// 将Program对象与ServiceConfig对象绑定,并创建一个新的Service对象
	s, err := service.New(prog, svcConfig)
	if err != nil {
		log.Fatal(err)
	}

	// 如果命令行参数为install、start、stop或restart,则执行对应的操作
	// 如果没有命令行参数,则输出命令行帮助信息
	switch os.Args[1] {
	case "install":
		err = s.Install()
		if err != nil {
			log.Fatal(err)
		}
		log.Printf("Service %s installed.", s.String())
	case "start":
		err = s.Start()
		if err != nil {
			log.Fatal(err)
		}
		log.Printf("Service %s started.", s.String())
	case "stop":
		err = s.Stop()
		if err != nil {
			log.Fatal(err)
		}
		log.Printf("Service %s stopped.", s.String())
	case "uninstall":
		err = s.Uninstall()
		if err != nil {
			log.Fatal(err)
		}
		log.Printf("Service %s uninstall.", s.String())
	case "run":
		err = s.Run()
		if err != nil {
			log.Fatal(err)
		}
		log.Printf("Service %s run.", s.String())
	case "restart":
		err = s.Stop()
		if err != nil {
			log.Fatal(err)
		}
		log.Printf("Service %s stopped.", s.String())
		err = s.Start()
		if err != nil {
			log.Fatal(err)
		}
		log.Printf("Service %s started.", s.String())
	default:
		log.Printf("Usage: %s install|uninstall|start|stop|restart", os.Args[0])
	}
}

func (p *Program) run() {
	file, err := os.OpenFile("E:\\Go\\GoPro\\src\\go_code\\work\\ziyi-Guard\\log.txt", os.O_WRONLY|os.O_RDONLY, os.ModePerm)
	defer file.Close()
	if err != nil {
		log.Println("open file err=", err)
		return
	}
	for {
		time.Sleep(time.Second * 5)
		file.WriteString(fmt.Sprintf(time.Now().String()) + " hello\n")
	}
}

3 tips

3.1去掉.exe运行的黑窗口

go build -ldflags "-s -w -H=windowsgui"
 
-s 省略符号表和调试信息
-w Omit the DWARF symbol table 省略DWARF符号表
-H windowsgui  不打印信息到console (On Windows, -H windowsgui writes a "GUI binary" instead of a "console binary."),就不会有cmd窗口了

3.2让exe程序以管理员身份运行(获取UAC)

在windows上执行有关系统设置命令的时候需要管理员权限才能操作,比如修改网卡的禁用、启用状态。双击执行是不能正确执行命令的,只有右键以管理员身份运行才能成功。

  • UAC:用户账户控制
① 不带图标
  1. 安装rsrc工具
go get github.com/akavel/rsrc
  1. nac.manifest 文件拷贝到当前windows项目根目录
    nac.manifest:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity
    version="9.0.0.0"
    processorArchitecture="x86"
    name="myapp.exe"
    type="win32"
/>
<description>myapp</description>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
    <security>
        <requestedPrivileges>
            <requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>
        </requestedPrivileges>
    </security>
</trustInfo>
</assembly>
  1. 执行rsrc命令生成.syso文件
rsrc -manifest nac.manifest -o nac.syso
  1. build文件

build的时候不带任何参数,即:go build

go build

如果写成指定文件编译–go build main.go 将无法成功获取UAC。(go build 在编译开始时,会搜索当前目录的 go 源码以及.syso文件,最后将所有资源一起打包到EXE文件。go build main.go 这种指定文件的编译命令,会编译指定文件和指定文件里面的所需要的依赖包,但是不会将.syso 文件打包到EXE。)如果,你的golang程序需要UAC权限或带GUI界面的,一定要注意正确使用编译命令!

最后的目录结构:
在这里插入图片描述

② 带图标
  1. 安装rsrc

  2. 将icon.ico图标文件放在与main.go文件相同的文件夹下

  3. 编写main.manifest

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity
    version="9.0.0.0"
    processorArchitecture="x86"
    name="myapp.exe"
    type="win32"
/>
<description>myapp</description>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
    <security>
        <requestedPrivileges>
            <requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>
        </requestedPrivileges>
    </security>
</trustInfo>
</assembly>

  1. 运行命令,生成.syso文件
 rsrc -arch amd64 -manifest main.manifest -ico main.ico -o main.syso

需要加上arch amd64,不然回报 is incompatible with i386:x86-64 output 错误

  1. 编译打包文件
go build -ldflags "-s -w" -o xguard.exe

最后目录结构:
在这里插入图片描述

注意:如果报错的话,可能是因为没有gcc环境,下载并配置即可

  • 下载链接:gcc下载地址

4 go-bindata使用

有时候我们需要将静态资源打包进.exe文件中,除了go后面官方提供的embed,还可以使用第三方组件:go-bindata

4.1 安装

1. go get -u github.com/go-bindata/go-bindata/...
// 引入fs(go-bindata-assetfs:提供fs服务)
2. go get github.com/elazarl/go-bindata-assetfs/...

4.2 打包生成bindata.go

# 将assets目录下的静态资源打包生成bindata/bindata.go文件中
go-bindata -o bindata/bindata.go -pkg bindata assets/...

4.3 main.go中引用bindata.go数据

项目结构:
在这里插入图片描述

func_file.go:

package service

import (
	"bytes"
	"compress/gzip"
	"fmt"
	"github.com/aobco/log"
	"github.com/getlantern/systray"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"ziyi.com/xguard/bindata"
)

func init() {
	// 注册静态文件处理函数
	http.HandleFunc("/", serveStaticFile)
}

// serveStaticFile 返回静态文件内容
func serveStaticFile(w http.ResponseWriter, r *http.Request) {
	// 获取文件路径
	filePath := filepath.Join(staticDir, r.URL.Path)
	log.Info("filePath:", filePath)
	// 从 bindata.go 文件中读取文件内容
	file, err := bindata.Asset(filePath)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	// 设置 Content-Type
	contentType := "text/html"
	if filepath.Ext(filePath) == ".css" {
		contentType = "text/css"
	} else if filepath.Ext(filePath) == ".js" {
		contentType = "application/javascript"
	}
	w.Header().Set("Content-Type", contentType)

	// 返回静态文件内容
	fmt.Fprint(w, string(file))
}
# 编译
go build main.go

参考:https://blog.csdn.net/mirage003/article/details/127581356

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/617221.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

iOS编译提效插件cocoapods-jxedt方案详解

1. 前言 本篇文章是cocoapods-jxedt插件实现方案的详解&#xff0c;主要从以下几个方面阐述了一下插件的实现方案和历程。 插件文件目录介绍插件的工作流程介绍插件实现过程中的问题和解决方案记录 如果你对插件的使用还不了解&#xff0c;建议先读一下cocoapods-jxedt使用介…

CVE漏洞复现-CVE-2023-32233 NetFilter权限提升

CVE-2023-32233 NetFilter权限提升 Netfilter是Linux 内核中的网络数据包处理框架&#xff08;iptables&#xff09;通过各种规则和过滤器&#xff0c;基于数据包的来源、目标地址、协议类型、端口号等信息&#xff0c;控制网络流量和数据包的转发和处理具体&#xff0c;详情请…

使用贝叶斯网络预测糖尿病:从理论到实践

2023年9月数学建模国赛期间提供ABCDE题思路加Matlab代码,专栏链接(赛前一个月恢复源码199,欢迎大家订阅):http://t.csdn.cn/Um9Zd 引言 在现实世界中&#xff0c;许多变量之间存在着复杂的概率关系&#xff0c;例如天气、交通、健康等方面的因素都会相互影响。为了更好地理解…

音频基本概念

1.音频信号 音频信号是一种连续变化的模拟信号&#xff0c;但计算机只能处理和记录二进制的数字信号&#xff0c;由自然音源得到的音频信号必须经过一定的变换&#xff0c;成为数字音频信号之后&#xff0c;才能送到计算机中作进一步的处理。 数字音频系统通过将声波的波型转换…

调用腾讯云API实现英文识别

目录 1. 作者介绍2. 腾讯云英文识别API介绍2.1 英文识别原理—OCR技术2.2 腾讯云英文识别API 3. 实验过程3.1获得API3.2申请调用接口3.3调试接口3.4实验代码3.5实验结果3.6 问题分析 4. 参考连接 1. 作者介绍 乔奕婕&#xff0c;女&#xff0c;西安工程大学电子信息学院&#…

nginx系列第六篇:结合nginx梳理linux中信号的使用

nginx中master进程来管理整个nginx工作的全过程&#xff0c;运行时其通过接收外部信号输入的方式来对内部进行相关调整。本文对照nginx来梳理一下linux中信号常用API的使用。 目录 1.函数sigaction和signal 2.关于信号集sigset_t 2.1 测试程序1 2.2 测试程序1 3.信号屏蔽…

宝塔安装ModStart常见问题

Q&#xff1a;环境提示PHP未禁用危险函数 安装系统时通常会需要解禁 system,exec,passthru,shell_exec,popen,proc_open 等危险函数。部分集成环境会提示危险函数风险&#xff0c;通常可以如下方式解决&#xff1a; 该函数在通常只是在系统 系统安装/系统升级/模块安装/模块升…

【SpinalHDL快速入门】4.3、基本类型之UInt/SInt

文章目录 1.1、描述1.2、声明1.3、运算符1.3.1、逻辑运算&#xff08;Logic&#xff09;1.3.2、算术运算&#xff08;Arithmetic&#xff09;1.3.3、比较&#xff08;Comparison&#xff09;1.3.4、类型转换&#xff08;Type Cast&#xff09;1.3.5、部分赋值/提取操作符&#…

第一章:数据库概述

第一章&#xff1a;数据库概述 1.1&#xff1a;为什么要使用数据库 持久化(persistence)&#xff1a;把数据保存到可掉电式存储设备中以供之后使用。大多数情况下&#xff0c;特别是企业级应用&#xff0c;数据持久化意味着将内存中的数据保存到硬盘上加以"固化"&a…

低代码平台简单分享

低代码平台简单分享 文章目录 低代码平台简单分享1、什么是低代码&#xff1f;什么是低代码平台&#xff1f;2、低代码平台的前世今生**一、低代码的起源**二、低代码的分类三、低代码的能力四、低代码开发的特点 3、目前主流的低代码平台有哪些&#xff1f;优缺点&#xff1f;…

常用模拟低通滤波器的设计——契比雪夫II型滤波器

常用模拟低通滤波器的设计——契比雪夫II型滤波器 切比雪夫 II 型滤波器的振幅平方函数为&#xff1a; 式中&#xff0c;为有效带通截止频率&#xff0c; 是与通带波纹有关的参量&#xff0c; 大&#xff0c;波纹大&#xff0c;&#xff1b; 为 N 阶契比雪夫多项式。 在 Matl…

几种常见数据库的表和列信息查询

文章目录 前言1. oracle数据库1.1 表信息和注释信息1.2 表的列信息 2. mysql数据库2.1 常用的几个命令2.2 使用desc查看表结构2.3 表结构信息主要存在information_schema数据库2.4 主要表是columns&#xff0c;tables&#xff0c;schemata2.4.1 schemata 数据库信息2.4.2 table…

三、opengles画三角形

第一部分Java端 1&#xff09;界面 <?xml version"1.0" encoding"utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android"http://schemas.android.com/apk/res/android"xmlns:app"http://schemas.andro…

数据中台浅析——概念、架构以及未来

数据中台浅析 1. 引言 在当今的数字化时代&#xff0c;数据被誉为"新的石油"&#xff0c;越来越多的企业和组织开始深度挖掘数据的价值。在这个过程中&#xff0c;数据中台逐渐成为了数据管理和分析的核心架构&#xff0c;让我们来深入了解一下它。 1.1 数据中台…

算法复杂度分析(一)

求第n个斐波那契数列 斐波那契数 0 1 1 2 3 5 数列默认从0开始 public static int fib1(int n) {if(n < 1) return n;return fib1(n-1) fib1(n-2);}public static int fib2(int n) {if(n < 1) return n;int first 0;int secend 1;for (int i 0; i < n-1; i) {int…

七、帧缓冲离屏渲染

第一部分基础概念 1)两种帧缓冲的由来 首先opengl能够显示到屏幕&#xff0c;也是有一个默认的framebuffer由窗口系统创建并管理的&#xff0c;将数据放到默认framebuffer 中就可以显示到屏幕上。但是应用程序也想创建额外的非可显示的framebuffer。 应用程序自己创建FBO也是…

【2023RT-Thread全球技术峰会】一套全新的物联网多应用框架xiotman,助你解决多应用的难题

写在前面 就在上周&#xff0c;我作为讲师参与了2023RT-Thread全球技术峰会的主题演讲&#xff0c;我给大家带来了一套全新的解决物联网终端应用多样化的软件架构解决方案&#xff0c;在这里我再次以图文的形式介绍一下给社区的小伙伴&#xff0c;希望借此机会找到更多的同频小…

Nginx优化、Nginx+Tomcat实现负载均衡、动静分离集群部署

Nginx优化、NginxTomcat实现负载均衡、动静分离集群部署 一、Tomcat 优化二、Tomcat多实例部署1、安装好jdk2、安装tomcat3、配置tomcat环境变量4、修改tomcat2中的server.xml文件&#xff0c;要求各tomcat实例配置不能有重复的端口号5、修改各tomcat实例中的startup.sh和shutd…

ARM--$2$驱动模块

目录 1.驱动模块&#xff08;驱动程序的框架&#xff09; 2.内核中的打印函数&#xff08;编写第一个驱动程序&#xff09; Source Insight 使用&#xff1a; 打印函数编写 分析 3.驱动的多文件编译 4.模块传递参数 安装好驱动之后如何传参&#xff1f; 多驱动之间调用…

js内存管理与闭包

JavaScript内存管理 ◼ 不管什么样的编程语言&#xff0c;在代码的执行过程中都是需要给它分配内存的&#xff0c;不同的是某些编程语言需要我们自己手动的管理内存&#xff0c; 某些编程语言会可以自动帮助我们管理内存&#xff1a; ◼ 不管以什么样的方式来管理内存&#xf…