golang使用walk编写的博客搜索查看的windows下的GUI软件

timzzx · · 10082 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

为了学习golang,我使用了golang walk这个windows下的GUI包实现了一款博客搜索查看的小软件。 **先上个展示图** ![输入图片说明](https://static.oschina.net/uploads/img/201708/25121015_VrPe.png "在这里输入图片标题") ![输入图片说明](https://static.oschina.net/uploads/img/201708/25121037_6gZp.png "在这里输入图片标题") **实现了几个功能** 1. 可以查询五个博客网站 2.可以翻页 3.有收藏功能 ``` // Copyright 2012 The Walk Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package main import ( "encoding/json" "io/ioutil" "log" "net/http" "net/url" "os" "path/filepath" "regexp" "strconv" "strings" "time" "github.com/PuerkitoBio/goquery" "github.com/lxn/walk" "github.com/russross/blackfriday" . "github.com/lxn/walk/declarative" ) func main() { //main mw := &MyMainWindow{} if err := (MainWindow{ Icon: "img/search.ico", AssignTo: &mw.MainWindow, Title: "BolgSearch -- by tim_zhang", MenuItems: []MenuItem{ Menu{ Text: "&编辑", Items: []MenuItem{ Separator{}, Action{ Text: "退出", OnTriggered: func() { mw.Close() }, }, }, }, Menu{ Text: "&收藏", Items: []MenuItem{ Separator{}, Action{ Text: "查看", OnTriggered: mw.search, }, }, }, Menu{ Text: "&帮助", Items: []MenuItem{ Action{ Text: "关于", OnTriggered: mw.aboutAction_Triggered, }, }, }, }, MinSize: Size{1000, 600}, Layout: VBox{MarginsZero: true}, Children: []Widget{ Composite{ MaxSize: Size{0, 50}, Layout: HBox{}, Children: []Widget{ PushButton{ AssignTo: &mw.favorites, Text: "收藏", }, PushButton{ AssignTo: &mw.unfavorites, Text: "取消收藏", }, Label{Text: "网站: "}, RadioButtonGroup{ DataMember: "Web", Buttons: []RadioButton{ RadioButton{ Name: "all", Text: "全部", Value: "1", AssignTo: &mw.all, }, RadioButton{ Name: "jianshu", Text: "简书", Value: "2", AssignTo: &mw.jianshu, }, RadioButton{ Name: "juejin", Text: "掘金", Value: "3", AssignTo: &mw.juejin, }, RadioButton{ Name: "bokeyuan", Text: "博客园", Value: "4", AssignTo: &mw.bokeyuan, }, RadioButton{ Name: "csdn", Text: "CSDN", Value: "5", AssignTo: &mw.csdn, }, RadioButton{ Name: "oschina", Text: "OSCHINA", Value: "6", AssignTo: &mw.os, }, }, }, Label{Text: "关键词: "}, LineEdit{ AssignTo: &mw.keywords, Text: "golang", }, PushButton{ AssignTo: &mw.query, Text: "查询", }, PushButton{ AssignTo: &mw.nextquery, Text: "下一页", }, PushButton{ AssignTo: &mw.prvquery, Text: "上一页", }, }, }, Composite{ Layout: Grid{Columns: 2, Spacing: 10}, Children: []Widget{ ListBox{ MaxSize: Size{200, 0}, AssignTo: &mw.lb, OnCurrentIndexChanged: mw.lb_CurrentIndexChanged, OnItemActivated: mw.lb_ItemActivated, }, WebView{ //MinSize: Size{1000, 0}, AssignTo: &mw.wv, }, }, }, }, }.Create()); err != nil { log.Fatal(err) } mw.query.Clicked().Attach(func() { go func() { mw.GetList(1) }() }) mw.nextquery.Clicked().Attach(func() { go func() { mw.page = mw.page + 1 mw.GetList(mw.page) }() }) mw.prvquery.Clicked().Attach(func() { go func() { mw.page = mw.page - 1 if mw.page == 0 { mw.page = 1 } mw.GetList(mw.page) }() }) mw.favorites.Clicked().Attach(func() { go func() { mw.addFavorite() mw.favorites.SetEnabled(false) mw.unfavorites.SetEnabled(true) walk.MsgBox(mw, "提示", "收藏成功", walk.MsgBoxIconInformation) }() }) mw.unfavorites.Clicked().Attach(func() { go func() { mw.delFavorite() mw.favorites.SetEnabled(true) mw.unfavorites.SetEnabled(false) walk.MsgBox(mw, "提示", "取消收藏", walk.MsgBoxIconWarning) }() }) //初始化 mw.wv.SetURL("file:///" + getCurrentDirectory() + "/data/init.html") mw.all.SetChecked(true) mw.favorites.SetEnabled(false) mw.unfavorites.SetEnabled(false) mw.curtitle = "" mw.cururl = "" mw.Run() } type MyMainWindow struct { *walk.MainWindow lb *walk.ListBox te *walk.TextEdit wv *walk.WebView all *walk.RadioButton jianshu *walk.RadioButton juejin *walk.RadioButton bokeyuan *walk.RadioButton csdn *walk.RadioButton os *walk.RadioButton keywords *walk.LineEdit model *Model query *walk.PushButton nextquery *walk.PushButton prvquery *walk.PushButton favorites *walk.PushButton unfavorites *walk.PushButton page int curtitle string cururl string } type JianshuList struct { Q string `json:"q"` Page int `json:"page"` Type string `json:"type"` TotalCount int `json:"total_count"` PerPage int `json:"per_page"` TotalPages int `json:"total_pages"` OrderBy string `json:"order_by"` Entries []struct { ID int `json:"id"` Title string `json:"title"` Slug string `json:"slug"` Content string `json:"content"` User struct { ID int `json:"id"` Nickname string `json:"nickname"` Slug string `json:"slug"` AvatarURL string `json:"avatar_url"` } `json:"user"` Notebook struct { ID int `json:"id"` Name string `json:"name"` } `json:"notebook"` Commentable bool `json:"commentable"` PublicCommentsCount int `json:"public_comments_count"` LikesCount int `json:"likes_count"` ViewsCount int `json:"views_count"` TotalRewardsCount int `json:"total_rewards_count"` FirstSharedAt time.Time `json:"first_shared_at"` } `json:"entries"` RelatedUsers []struct { ID int `json:"id"` AvatarURL string `json:"avatar_url"` Nickname string `json:"nickname"` Slug string `json:"slug"` TotalWordage int `json:"total_wordage"` TotalLikesCount int `json:"total_likes_count"` } `json:"related_users"` MoreRelatedUsers bool `json:"more_related_users"` RelatedCollections []struct { ID int `json:"id"` Title string `json:"title"` Slug string `json:"slug"` ImageURL string `json:"image_url"` PublicNotesCount int `json:"public_notes_count"` LikesCount int `json:"likes_count"` } `json:"related_collections"` MoreRelatedCollections bool `json:"more_related_collections"` } type JuejinList struct { D []struct { CollectionCount int `json:"collectionCount"` OriginalURL string `json:"originalUrl"` CommentsCount int `json:"commentsCount"` User struct { ObjectID string `json:"objectId"` JobTitle string `json:"jobTitle"` Username string `json:"username"` } `json:"user"` ObjectID string `json:"objectId"` Content string `json:"content"` Title string `json:"title"` CreatedAt time.Time `json:"createdAt"` Tags []interface{} `json:"tags"` UpdatedAt time.Time `json:"updatedAt"` } `json:"d"` M string `json:"m"` S int `json:"s"` } type Item struct { name string value string } type Items struct { items []Item } type Model struct { walk.ListModelBase items []Item } func (m *Model) ItemCount() int { return len(m.items) } func (m *Model) Value(index int) interface{} { return m.items[index].name } func (mw *MyMainWindow) aboutAction_Triggered() { walk.MsgBox(mw, "关于", "BlogSearch阅读器v1.0\n作者:tim_zhang 完成时间:2017-8-25 日", walk.MsgBoxIconQuestion) } func (mw *MyMainWindow) search() { mw.GetList(-999) } func (mw *MyMainWindow) GetList(page int) { mw.page = page keywords := mw.keywords.Text() enkeywords := url.QueryEscape(keywords) //mw.wv.SetURL("file:///" + getCurrentDirectory() + "/data/init.html") defer func() { mw.lb.SetCurrentIndex(-1) }() if page == -999 { go func() { rf := mw.readFavorite() m := &Model{items: rf.items} mw.lb.SetModel(m) mw.model = m }() return } if mw.all.Checked() == true { go func() { jianshu := getjianshList(enkeywords, strconv.Itoa(page)) juejin := getjuejinList(enkeywords, strconv.Itoa(page)) bokeyuan := getbokeyuanList(enkeywords, strconv.Itoa(page)) csdn := getcsdnList(enkeywords, strconv.Itoa(page)) os := getosList(enkeywords, strconv.Itoa(page)) var k = 0 var lens = 0 if len(jianshu.items) > 0 { lens += len(jianshu.items) } if len(juejin.items) > 0 { lens += len(juejin.items) } if len(bokeyuan.items) > 0 { lens += len(bokeyuan.items) } if len(csdn.items) > 0 { lens += len(csdn.items) } if len(os.items) > 0 { lens += len(os.items) } m := &Model{items: make([]Item, lens)} if len(jianshu.items) > 0 { for _, jian := range jianshu.items { m.items[k] = jian k++ } } if len(juejin.items) > 0 { for _, jue := range juejin.items { m.items[k] = jue k++ } } if len(bokeyuan.items) > 0 { for _, bo := range bokeyuan.items { m.items[k] = bo k++ } } if len(csdn.items) > 0 { for _, cs := range csdn.items { m.items[k] = cs k++ } } if len(os.items) > 0 { for _, osl := range os.items { m.items[k] = osl k++ } } mw.lb.SetModel(m) mw.model = m }() return } if mw.jianshu.Checked() == true { go func() { jianshu := getjianshList(enkeywords, strconv.Itoa(page)) m := &Model{items: jianshu.items} mw.lb.SetModel(m) mw.model = m }() return } if mw.juejin.Checked() == true { go func() { juejin := getjuejinList(enkeywords, strconv.Itoa(page)) m := &Model{items: juejin.items} mw.lb.SetModel(m) mw.model = m }() return } if mw.bokeyuan.Checked() == true { go func() { bokeyuan := getbokeyuanList(enkeywords, strconv.Itoa(page)) m := &Model{items: bokeyuan.items} mw.lb.SetModel(m) mw.model = m }() return } if mw.csdn.Checked() == true { go func() { csdn := getcsdnList(enkeywords, strconv.Itoa(page)) m := &Model{items: csdn.items} mw.lb.SetModel(m) mw.model = m }() return } if mw.os.Checked() == true { go func() { os := getosList(enkeywords, strconv.Itoa(page)) m := &Model{items: os.items} mw.lb.SetModel(m) mw.model = m }() return } return } func (mw *MyMainWindow) lb_CurrentIndexChanged() { i := mw.lb.CurrentIndex() if i < 0 { return } //defer mw.lb.SetCurrentIndex(-1) item := &mw.model.items[i] s := strings.Split(item.value, "|") if len(s) == 2 { switch s[0] { case "jianshu": mw.getjianshu(item.name, s[1]) case "juejin": mw.getjuejin(item.name, s[1]) case "bokeyuan": mw.getbokeyuan(item.name, s[1]) case "csdn": mw.getcsdn(item.name, s[1]) case "os": mw.getos(item.name, s[1]) } //记录当前页面的标题和链接 mw.curtitle = item.name mw.cururl = item.value if mw.checkFavorite() { mw.favorites.SetEnabled(false) mw.unfavorites.SetEnabled(true) } else { mw.favorites.SetEnabled(true) mw.unfavorites.SetEnabled(false) } } else { mw.curtitle = "" mw.cururl = "" mw.favorites.SetEnabled(false) mw.unfavorites.SetEnabled(false) mw.wv.SetURL("file:///" + getCurrentDirectory() + "/data/xxx.html") } return } func (mw *MyMainWindow) lb_ItemActivated() { mw.curtitle = "" mw.cururl = "" mw.favorites.SetEnabled(false) mw.unfavorites.SetEnabled(false) mw.wv.SetURL("file:///" + getCurrentDirectory() + "/data/xxx.html") } func (mw *MyMainWindow) getjianshu(name string, value string) { go func() { re, _ := regexp.Compile("<img[\\S\\s]+?>") userFile := "data/xxx.html" fout, err := os.Create(userFile) defer fout.Close() if err != nil { return } //walk.MsgBox(mw, "查询", "http://www.jianshu.com/p/"+value, walk.MsgBoxIconInformation) doc, err := goquery.NewDocument("http://www.jianshu.com/p/" + value) if err != nil { fout.WriteString("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" /><h3>" + name + "————简书</h3><a href='http://www.jianshu.com/p/" + value + "' target='_blank'>文章地址</a>") } ht, _ := doc.Find(".show-content").Html() fout.WriteString("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" /><h3>" + name + "————简书</h3><a href='http://www.jianshu.com/p/" + value + "' target='_blank'>文章地址</a>" + re.ReplaceAllString(ht, "")) mw.wv.SetURL("file:///" + getCurrentDirectory() + "/data/xxx.html") }() } func (mw *MyMainWindow) getjuejin(name string, value string) { go func() { userFile := "data/xxx.html" fout, err := os.Create(userFile) defer fout.Close() if err != nil { return } //walk.MsgBox(mw, "查询", "https://juejin.im/entry/"+value, walk.MsgBoxIconInformation) doc, err := goquery.NewDocument("https://juejin.im/entry/" + value) if err != nil { //log.Fatal(err) fout.WriteString("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" /><h3>" + name + " ————掘金</h3><a href='https://juejin.im/entry/" + value + "' target='_blank'>文章地址</a>") } ht, _ := doc.Find(".entry-content").Html() re, _ := regexp.Compile("<img[\\S\\s]+?>") fout.WriteString("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" /><h3>" + name + "————掘金</h3><a href='https://juejin.im/entry/" + value + "' target='_blank'>文章地址</a>" + re.ReplaceAllString(ht, "")) mw.wv.SetURL("file:///" + getCurrentDirectory() + "/data/xxx.html") }() } func (mw *MyMainWindow) getbokeyuan(name string, value string) { go func() { userFile := "data/xxx.html" fout, err := os.Create(userFile) defer fout.Close() if err != nil { return } //walk.MsgBox(mw, "查询", value, walk.MsgBoxIconInformation) doc, err := goquery.NewDocument(value) if err != nil { //log.Fatal(err) fout.WriteString("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" /><h3>" + name + " ————博客园</h3><a href='" + value + "' target='_blank'>文章地址</a>") } ht, _ := doc.Find("#cnblogs_post_body").Html() re, _ := regexp.Compile("<img[\\S\\s]+?>") fout.WriteString("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" /><h3>" + name + "————博客园</h3><a href='" + value + "' target='_blank'>文章地址</a>" + re.ReplaceAllString(ht, "")) mw.wv.SetURL("file:///" + getCurrentDirectory() + "/data/xxx.html") }() } func (mw *MyMainWindow) getcsdn(name string, value string) { go func() { userFile := "data/xxx.html" fout, err := os.Create(userFile) defer fout.Close() if err != nil { return } //walk.MsgBox(mw, "查询", value, walk.MsgBoxIconInformation) doc, err := goquery.NewDocument(value) if err != nil { //log.Fatal(err) fout.WriteString("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" /><h3>" + name + " ————CSDN</h3><a href='" + value + "' target='_blank'>文章地址</a>") } ht, _ := doc.Find("#article_content").Html() re, _ := regexp.Compile("<img[\\S\\s]+?>") hs := re.ReplaceAllString(ht, "") res, _ := regexp.Compile("<script[\\S\\s]+?>") fout.WriteString("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" /><h3>" + name + "————CSDN</h3><a href='" + value + "' target='_blank'>文章地址</a>" + res.ReplaceAllString(hs, "")) mw.wv.SetURL("file:///" + getCurrentDirectory() + "/data/xxx.html") }() } func (mw *MyMainWindow) getos(name string, value string) { go func() { userFile := "data/xxx.html" fout, err := os.Create(userFile) defer fout.Close() if err != nil { return } //walk.MsgBox(mw, "查询", value, walk.MsgBoxIconInformation) doc, err := goquery.NewDocument(value) if err != nil { //log.Fatal(err) fout.WriteString("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" /><h3>" + name + " ————OSCHINA</h3><a href='" + value + "' target='_blank'>文章地址</a>") } ht, _ := doc.Find(".blog-body").Html() hs, _ := doc.Find(".noshow_content").Html() if len(hs) != 0 { ht = string(blackfriday.MarkdownBasic([]byte(hs))) } re, _ := regexp.Compile("<img[\\S\\s]+?>") fout.WriteString("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" /><h3>" + name + "————OSCHINA</h3><a href='" + value + "' target='_blank'>文章地址</a>" + re.ReplaceAllString(ht, "")) mw.wv.SetURL("file:///" + getCurrentDirectory() + "/data/xxx.html") }() } func httpDo(method string, url string) (bodys string) { client := &http.Client{} req, err := http.NewRequest(method, url, nil) if err != nil { } if method == "POST" { req.Header.Set("Content-Type", "application/x-www-form-urlencoded") } else { req.Header.Set("Accept", "application/json") } req.Header.Set("Cache-Control", "no-cache") req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36") resp, err := client.Do(req) defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) bodys = string(body) if err != nil { // handle error return "" } // fmt.Println(string(body)) return bodys } func (mw *MyMainWindow) addFavorite() { mw.delFavorite() rf := mw.readFavorite() st := mw.curtitle + "<$>" + mw.cururl + "$$" for i, ite := range rf.items { if i == len(rf.items)-1 { st = st + ite.name + "<$>" + ite.value + "$$" } else { st = st + ite.name + "<$>" + ite.value } } userFile := "data/favorite" fout, err := os.Create(userFile) defer fout.Close() if err != nil { return } fout.WriteString(st) } func (mw *MyMainWindow) delFavorite() { rf := mw.readFavorite() sb := mw.curtitle + "<$>" + mw.cururl st := "" for i, ite := range rf.items { if sb != ite.name+"<$>"+ite.value { if i == 0 { st = st + ite.name + "<$>" + ite.value } else { st = st + "$$" + ite.name + "<$>" + ite.value } } } userFile := "data/favorite" fout, err := os.Create(userFile) defer fout.Close() if err != nil { return } fout.WriteString(st) } func (mw *MyMainWindow) readFavorite() Items { f, _ := os.Open("data/favorite") fd, _ := ioutil.ReadAll(f) fs := string(fd) s := strings.Split(fs, "$$") if len(s[0]) > 0 { data := Items{items: make([]Item, len(s))} for i, ite := range s { if len(ite) > 0 { b := strings.Split(ite, "<$>") data.items[i].name = b[0] data.items[i].value = b[1] } } return data } else { data := Items{} return data } } func (mw *MyMainWindow) checkFavorite() bool { rf := mw.readFavorite() sb := mw.curtitle + "<$>" + mw.cururl st := false for _, ite := range rf.items { if sb == ite.name+"<$>"+ite.value { st = true } } return st } func getjianshList(keywords string, page string) Items { body := httpDo("GET", "http://www.jianshu.com/search/do?q="+keywords+"&type=note&page="+page+"&order_by=default") var r JianshuList json.Unmarshal([]byte(body), &r) data := Items{items: make([]Item, len(r.Entries))} re, _ := regexp.Compile("\\<[\\S\\s]+?\\>") for i, en := range r.Entries { data.items[i] = Item{re.ReplaceAllString(en.Title, ""), re.ReplaceAllString("jianshu|"+en.Slug, "")} } return data } func getjuejinList(keywords string, page string) Items { body := httpDo("GET", "https://search-merger-ms.juejin.im/v1/search?query="+keywords+"&page="+page+"&raw_result=false&src=web") var r JuejinList json.Unmarshal([]byte(body), &r) data := Items{items: make([]Item, len(r.D))} re, _ := regexp.Compile("\\<[\\S\\s]+?\\>") for i, en := range r.D { data.items[i] = Item{re.ReplaceAllString(en.Title, ""), re.ReplaceAllString("juejin|"+en.ObjectID, "")} } return data } func getbokeyuanList(keywords string, page string) Items { data := Items{items: make([]Item, 15)} re, _ := regexp.Compile("\\<[\\S\\s]+?\\>") doc, _ := goquery.NewDocument("http://zzk.cnblogs.com/s/blogpost?Keywords=" + keywords + "&pageindex=" + page) doc.Find(".searchItemTitle").Each(func(i int, s *goquery.Selection) { title := s.Text() value, _ := s.Find("a").Attr("href") data.items[i] = Item{strings.Replace(re.ReplaceAllString(title, ""), " ", "", -1), "bokeyuan|" + value} }) return data } func getcsdnList(keywords string, page string) Items { data := Items{items: make([]Item, 10)} re, _ := regexp.Compile("\\<[\\S\\s]+?\\>") doc, _ := goquery.NewDocument("http://so.csdn.net/so/search/s.do?p=" + page + "&q=" + keywords + "&t=blog&domain=&o=&s=&u=null&l=null&f=null") doc.Find(".search-list").Each(func(i int, s *goquery.Selection) { title := s.Find("a").Text() value, _ := s.Find("a").Attr("href") data.items[i] = Item{re.ReplaceAllString(title, ""), "csdn|" + value} }) return data } func getosList(keywords string, page string) Items { data := Items{items: make([]Item, 20)} body := httpDo("GET", "https://www.oschina.net/search?scope=blog&sort_by_time=1&q="+keywords+"&p="+page) digitsRegexp := regexp.MustCompile(`"(.*)"`) s := digitsRegexp.FindStringSubmatch(body) re, _ := regexp.Compile("\\<[\\S\\s]+?\\>") doc, _ := goquery.NewDocument("https://www.oschina.net/search" + s[1]) doc.Find(".obj_type_3").Each(func(i int, s *goquery.Selection) { title := s.Find("a").Text() value, _ := s.Find("a").Attr("href") data.items[i] = Item{re.ReplaceAllString(title, ""), "os|" + value} }) return data } func getCurrentDirectory() string { dir, err := filepath.Abs(filepath.Dir(os.Args[0])) if err != nil { log.Fatal(err) } return strings.Replace(dir, "\\", "/", -1) } ``` 上面就是golang的代码。 github上的项目地址:[BlogSearch](https://github.com/timzzx/BlogSearch) oschina上的项目地址:[BlogSearch](https://gitee.com/timx/BlogSearch) 说说我遇到的一些坑。 1.walk这个项目非常好,不过例子很少。毕竟现在是移动开发的天下,桌面级应用少了很多,无奈。 2.walk中的webview很简陋js就不要想着运行了,我展示时候去掉了,不然会报错。 3.walk中的webview的图片显示到时没有太大问题,不过我展示时候还是去掉了,为什么请看下一条。 4.walk中的webview使用SetUrl改变了URL重新加载html后,如果加载未完成就重新Set的话程序会崩溃。 暂时没有找到合适的解决办法,这就是我去掉图片的原因加载图片慢,并且我看到walk的issue中有个提问是关于webview的内存不够也会导致程序崩溃,提问者提给golang官方,貌似还没有解决。 5.listbox 中 如果不加mw.lb.SetCurrentIndex(-1)也会导致重新更新list时候,程序崩溃 6.listbox中点击时候如果没有点击到item上也会崩溃。我的解决办法就是 i := mw.lb.CurrentIndex() if i < 0 { return } 其余就没有什么问题了。 walk我个人的感觉挺好的,只是缺乏一些好的复杂实例来学习研究,靠看源码进行开发这是个累人的办法/(ㄒoㄒ)/~~ **最后说一句**我只是为了学习golang开发了这款小软件,不做任何商业用途,也不会去破坏什么一个网站。希望大家理解:-D

有疑问加站长微信联系(非本文作者))

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

10082 次点击  ∙  1 赞  
加入收藏 微博
7 回复  |  直到 2020-05-15 11:22:04
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传