go

您所在的位置:网站首页 golang跨平台gui库 go

go

2023-06-16 08:50| 来源: 网络整理| 查看: 265

go-GUI框架:fyne教程及解决中文乱码等常见bug 1 fyne教程 fyne教程地址: https://www.topgoer.cn/docs/goday/goday-1crdp17nj4v6phttps://pkg.go.dev/fyne.io/fyne/v2#section-readme 1.1 介绍 简单易用,fyne提供了简单直观的API,可以快速学习并上手进行开发跨平台支持,fyne提供了多个平台的支持,包括:Windows、MacOs、Linux等响应式布局,提供了灵活的布局管理器,我们可以根据需要动态调整页面元素的位置和大小丰富的组件库,fyne内置许多常用且美观的组件库,大大降低了一个GUI的开发成本可扩展性高,允许我们自定义渲染器、主题和样式,最大程度上的对页面进行DIY事件处理强大,提供了丰富的事件处理机制和系统托盘,我们可以通过监听用户输入、按钮点击等事件来实现与用户的交互社区活跃和文档丰富,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 case flag := 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 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 ①解决中文乱码问题(编译打包可用) 导入库 import "github.com/flopp/go-findfont" 添加初始化代码 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界面,通常由用户手动启动和停止。

注册服务 # 注册一个ServiceTest服务 sc create ServiceTest binpath="/path/to/exe" 启动服务 sc start ServiceTest 停止、删除服务 sc stop ServiceTest sc delete ServiceTest 2.2 golang注册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") } } 2.3 检测服务是否存在 ①windows package main import ( "fmt" "os/exec" "strings" ) func main() { var serviceName string fmt.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 main import ( "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.Buffer cmd.Stdout = &out err = 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 main import ( "bytes" "fmt" "os/exec" ) func main() { // 判断sshd服务是否存在 cmd := exec.Command("launchctl", "list", "ssh") var out bytes.Buffer cmd.Stdout = &out err := 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:用户账户控制 ① 不带图标 安装rsrc工具 go get github.com/akavel/rsrc nac.manifest 文件拷贝到当前windows项目根目录 nac.manifest: myapp 执行rsrc命令生成.syso文件 rsrc -manifest nac.manifest -o nac.syso 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界面的,一定要注意正确使用编译命令!

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

② 带图标

安装rsrc

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

编写main.manifest

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

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

编译打包文件 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 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



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3