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

2023-10-19 05:40

本文主要是介绍go-GUI开发:fyne解决中文乱码+注册windows服务,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

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 mainimport ("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.Argsif len(args) >= 2 {arg := args[1]//根据参数做对应操作service.SelectXGuard(arg)} else {app := ui.NewGuardApp()app.W.ShowAndRun()}}
②sys_service.go

注册windows服务

package serviceimport ("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 uiimport ("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.urlname := data.nametags := 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 <- falsereturn false}isConnected <- truereturn 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 uiimport ("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    stringguardConfig = 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 += vguardResData += 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 := 1for {if index <= dirCount {options = append(options, fmt.Sprintf("agent%v", index))index++} else {break}}index = 0choices := make([]string, dirCount)for _, v := range options {if v != "" {choices[index] = vindex++}}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 uiimport ("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.ResourcetrayMenu 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 uiimport ("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()//复原mainMenuMakeMainUI(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 uiimport ("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  stringname stringtags 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.TabItemtmpUrl := ""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.Textname := nameEntry.Texttags := tagEntry.Textlog.Infof("用户提交ziyi-guard配置信息 url:%s,name:%s,tags:%s\n", url, name, tags)//更新showTab内容url = urlEntry.Textname = nameEntry.Texttags = 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.Textif text != "" {tmpUrl = texttmpIp := 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 uiimport ("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.AppW fyne.Window
}func NewGuardApp() *GuardApp {myApp := &GuardApp{}a := app.New()//主题设置a.Settings().SetTheme(theme.DarkTheme())w := a.NewWindow("配置信息")myApp.W = wiconData, 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服务

2.1cmd操作windows服务

  • Windows Service可以在操作系统启动的时候开始,一直在后台运行,当有需要时也可以手动启动,我们可以通过管理工具里面的服务进行统一管理。
  • 当系统启动完毕后,Windows服务并不需要通过登陆页面后才能启动,即使用户注销登录也不会停止,通常不和用户产生交互。

而我们启动一般的exe文件却要先登陆Windows后才能启动它,通常还有一个用户界面,命令行或者是GUI界面,通常由用户手动启动和停止。

  1. 注册服务
# 注册一个ServiceTest服务
sc create ServiceTest binpath="/path/to/exe"
  1. 启动服务
sc start ServiceTest
  1. 停止、删除服务
sc stop ServiceTest
sc delete ServiceTest

2.2 golang注册windows服务

package mainimport ("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("安装服务成功")}returncase "uninstall":err := s.Uninstall()if err != nil {fmt.Println("卸载服务失败: ", err.Error())} else {fmt.Println("卸载服务成功")}returncase "start":err := s.Start()if err != nil {fmt.Println("运行服务失败: ", err.Error())} else {fmt.Println("运行服务成功")}returncase "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 mainimport ("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")}
}

2.3 检测服务是否存在

①windows
package mainimport ("fmt""os/exec""strings"
)func main() {var serviceName stringfmt.Println("please enter the serviceName you want to check:")fmt.Scanln(&serviceName)// 判断AJRouter服务是否启动cmd := exec.Command("sc", "query", serviceName)cmdOutput, err := cmd.Output()if err != nil {fmt.Printf("%v is not installed\n", serviceName)return} else {fmt.Printf("%v is installed\n", serviceName)}if strings.Contains(string(cmdOutput), "RUNNING") {fmt.Printf("%v is running\n", serviceName)} else if strings.Contains(string(cmdOutput), "STOPPED") {fmt.Printf("%v is stopped\n", serviceName)}
}
②linux
package mainimport ("bytes""fmt""os/exec"
)func main() {// 判断sshd是否存在cmd := exec.Command("which", "sshd")err := cmd.Run()if err != nil {fmt.Println("sshd not found")return}// 判断sshd服务是否启动cmd = exec.Command("systemctl", "status", "sshd")var out bytes.Buffercmd.Stdout = &outerr = cmd.Run()if err != nil {fmt.Println("failed to check sshd status:", err)return}if out.String() == "" {fmt.Println("sshd is not running")} else {fmt.Println("sshd is running")}
}
③mac
package mainimport ("bytes""fmt""os/exec"
)func main() {// 判断sshd服务是否存在cmd := exec.Command("launchctl", "list", "ssh")var out bytes.Buffercmd.Stdout = &outerr := cmd.Run()if err != nil {fmt.Println("failed to check sshd service:", err)return}if out.String() != "" {fmt.Println("sshd service is installed")} else {fmt.Println("sshd service is not installed")}// 判断sshd服务是否启动cmd = exec.Command("launchctl", "list", "ssh")cmdOutput, err := cmd.Output()if err != nil {fmt.Println("failed to check sshd service:", err)return}statusStr := "status\t"if bytes.Contains(cmdOutput, []byte(statusStr+"0")) {fmt.Println("sshd service is stopped")} else if bytes.Contains(cmdOutput, []byte(statusStr+"1")) {fmt.Println("sshd service is running")} else {fmt.Println("failed to get sshd service status")}
}

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">
<assemblyIdentityversion="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">
<assemblyIdentityversion="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下载地址
③运行时不展示黑窗口
go build -ldflags "-H=windowsgui" main.go
//go build -ldflags "-s -w -H=windowsgui" -o test.exe

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 serviceimport ("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-TypecontentType := "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

这篇关于go-GUI开发:fyne解决中文乱码+注册windows服务的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/237698

相关文章

Go语言中最便捷的http请求包resty的使用详解

《Go语言中最便捷的http请求包resty的使用详解》go语言虽然自身就有net/http包,但是说实话用起来没那么好用,resty包是go语言中一个非常受欢迎的http请求处理包,下面我们一起来学... 目录安装一、一个简单的get二、带查询参数三、设置请求头、body四、设置表单数据五、处理响应六、超

SpringBoot利用dynamic-datasource-spring-boot-starter解决多数据源问题

《SpringBoot利用dynamic-datasource-spring-boot-starter解决多数据源问题》dynamic-datasource-spring-boot-starter是一... 目录概要整体架构构想操作步骤创建数据源切换数据源后续问题小结概要自己闲暇时间想实现一个多租户平台,

VSCode中C/C++编码乱码问题的两种解决方法

《VSCode中C/C++编码乱码问题的两种解决方法》在中国地区,Windows系统中的cmd和PowerShell默认编码是GBK,但VSCode默认使用UTF-8编码,这种编码不一致会导致在VSC... 目录问题方法一:通过 Code Runner 插件调整编码配置步骤方法二:在 PowerShell

mybatis-plus分页无效问题解决

《mybatis-plus分页无效问题解决》本文主要介绍了mybatis-plus分页无效问题解决,原因是配置分页插件的版本问题,旧版本和新版本的MyBatis-Plus需要不同的分页配置,感兴趣的可... 昨天在做一www.chinasem.cn个新项目使用myBATis-plus分页一直失败,后来经过多方

电脑开机提示krpt.dll丢失怎么解决? krpt.dll文件缺失的多种解决办法

《电脑开机提示krpt.dll丢失怎么解决?krpt.dll文件缺失的多种解决办法》krpt.dll是Windows操作系统中的一个动态链接库文件,它对于系统的正常运行起着重要的作用,本文将详细介绍... 在使用 Windows 操作系统的过程中,用户有时会遇到各种错误提示,其中“找不到 krpt.dll”

Windows系统下如何查找JDK的安装路径

《Windows系统下如何查找JDK的安装路径》:本文主要介绍Windows系统下如何查找JDK的安装路径,文中介绍了三种方法,分别是通过命令行检查、使用verbose选项查找jre目录、以及查看... 目录一、确认是否安装了JDK二、查找路径三、另外一种方式如果很久之前安装了JDK,或者在别人的电脑上,想

Spring Cloud之注册中心Nacos的使用详解

《SpringCloud之注册中心Nacos的使用详解》本文介绍SpringCloudAlibaba中的Nacos组件,对比了Nacos与Eureka的区别,展示了如何在项目中引入SpringClo... 目录Naacos服务注册/服务发现引⼊Spring Cloud Alibaba依赖引入Naco编程s依

基于Python开发PDF转PNG的可视化工具

《基于Python开发PDF转PNG的可视化工具》在数字文档处理领域,PDF到图像格式的转换是常见需求,本文介绍如何利用Python的PyMuPDF库和Tkinter框架开发一个带图形界面的PDF转P... 目录一、引言二、功能特性三、技术架构1. 技术栈组成2. 系统架构javascript设计3.效果图

Linux虚拟机不显示IP地址的解决方法(亲测有效)

《Linux虚拟机不显示IP地址的解决方法(亲测有效)》本文主要介绍了通过VMware新装的Linux系统没有IP地址的解决方法,主要步骤包括:关闭虚拟机、打开VM虚拟网络编辑器、还原VMnet8或修... 目录前言步骤0.问题情况1.关闭虚拟机2.China编程打开VM虚拟网络编辑器3.1 方法一:点击还原VM

Flask解决指定端口无法生效问题

《Flask解决指定端口无法生效问题》文章讲述了在使用PyCharm开发Flask应用时,启动地址与手动指定的IP端口不一致的问题,通过修改PyCharm的运行配置,将Flask项目的运行模式从Fla... 目录android问题重现解决方案问题重现手动指定的IP端口是app.run(host='0.0.