go walk 開發window界面,上傳文件到阿里雲oss -- 客戶端

前面我們完成了一個網頁端的上傳oss程序:https://blog.csdn.net/daily886/article/details/103366145

現在我們把前後端分離

前端使用walk開發,window界面

後臺服務器使用go接收圖片並上傳到oss   ,服務器端操作流程:https://blog.csdn.net/daily886/article/details/103409400

客戶端目錄結構如下: 

項目根目錄是 goossclient

goossclient的目錄結構

├── conf                             # 配置文件統一存放目錄
│   ├── config.yaml            # 配置文件
├── handler                        # 控制輸出到瀏覽器
│   ├── handler.go
├── model                          # 操作模型
│   ├── windowdialog.go    # window界面操作模型
├── goossclient.manifest   # walk需要使用的 manifest 文件
├── main.go                       # Go程序唯一入口

下面,我們根據目錄結構,從上往下建立文件夾和文件
建立文件夾和文件 goossclient/conf/config.yaml ,config.yaml 內容如下:

common:
  #服務器配置
  server:
    server: http://xxx.com         #服務器地址
    port: 6669               #服務器端口
    uploadapi: ossupload    #服務器上傳接口
    allowtype: [ #允許上傳的文件格式, *是所有
      jpg,
      png,
      gif,
#      "*", # * 代表所有文件
    ]


建立文件夾和文件 goossclient/handler/handler.go,handler.go內容如下:

package handler

import (
	"errors"
	"bytes"
	"net/http"
	"io/ioutil"
)

type Response struct {
	Code    int         `json:"code"`
	Message string      `json:"message"`
	Data    interface{} `json:"data"`
}

//http請求
func HttpRequest(api string,contenttype string,buff *bytes.Buffer,method string) (string, error) {
	//jsonStr := []byte(json)
	//req, err := http.NewRequest(method, api, bytes.NewBuffer(jsonStr))
	req, err := http.NewRequest(method, api, buff)
	if err != nil {
		return "", err
	}
	req.Header.Set("Content-Type", contenttype) //使用json格式傳參

	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	body, _ := ioutil.ReadAll(resp.Body)

	if !(resp.StatusCode == 200) {
		return "",  errors.New("api服務器出錯")
	}
	return string(body), nil
}


建立文件夾和文件 goossclient/model/windowdialog.go,windowdialog.go內容如下:

package model

import (
	"io"
	"os"
	"os/exec"
	"fmt"
	"time"
	"bytes"
	"strings"
	"strconv"
	"crypto/md5"
	"path/filepath"
	"mime/multipart"

	"github.com/tidwall/gjson"
	"github.com/spf13/viper"
	"github.com/lxn/walk"
	. "github.com/lxn/walk/declarative"

	. "goossclient/handler"
)

/*-------------------window界面-------------------*/
/*
# 不隱藏cmd 窗口
[root@localhost aichatwindow]# CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build

# 隱藏cmd 窗口
[root@localhost aichatwindow]# CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags="-H windowsgui"
*/
type MyMainWindow struct {
	*walk.MainWindow
	model *MessageModel
	selectedfile *walk.LineEdit
	message  *walk.ListBox
	wv *walk.WebView
	//imageview  *walk.ImageView
}

func OpenWindow() {
	mw := &MyMainWindow{model: NewMessageModel()}

	if _, err := (MainWindow{
		AssignTo: &mw.MainWindow,
		Title:    "阿里雲文件上傳",
		//Icon:     "test.ico", //圖標文件路徑
		MinSize:  Size{300, 400},
		Layout:   VBox{},
		Children: []Widget{
			GroupBox{
				MaxSize: Size{500, 500},
				Layout: HBox{},
				Children: []Widget{
					PushButton{ //選擇文件
						Text:      "打開文件",
						OnClicked: mw.selectFile, //點擊事件
					},
					Label{Text: "選中的文件 "},
					LineEdit{
						AssignTo: &mw.selectedfile, //選中的文件
					},
					PushButton{ //上傳
						Text:      "上傳",
						OnClicked: mw.uploadFile,  //上傳
					},
				},
			},
			ListBox{ //記錄框
				AssignTo: &mw.message,
				OnCurrentIndexChanged: mw.lb_CurrentIndexChanged, //單擊
				OnItemActivated:       mw.lb_ItemActivated, //雙擊
			},
			Composite{
				Layout: Grid{Columns: 2, Spacing: 10},
				Children: []Widget{
					WebView{
						//MinSize:  Size{1000, 0},
						AssignTo: &mw.wv,
					},
				},
			},
		},
	}.Run());
	err != nil {
		fmt.Printf("Run err: %+v\n",err)
	}
}


func (mw *MyMainWindow) selectFile() {
	allowType := viper.GetStringSlice("common.server.allowtype")
	fmt.Printf("allowType: %+v\n",allowType)

	dlg := new(walk.FileDialog)
	dlg.Title = "選擇文件"
	//dlg.Filter = "可上傳jpg (*.jpg)|*.jpg|可上傳png (*.png)|*.png|可上傳gif (*.gif)|*.gif|所有文件 (*.*)|*.*"
	//判斷可允許上傳的文件
	filter := []string{}
	filterstring := ""
	for _,v := range allowType{
		if(v != "*"){
			filterstring = "可上傳"+v+" (*."+v+")|*."+v
			filter = append(filter,filterstring)
		}else{
			filterstring = "所有文件"+v+" (*."+v+")|*."+v
			filter = append(filter,filterstring)
		}
	}
	dlg.Filter = strings.Join(filter,"|") //切片轉換字符串
	fmt.Printf("dlg.Filter: %+v\n",dlg.Filter)

	record := getMessage(mw) //獲取記錄

	if ok, err := dlg.ShowOpen(mw); err != nil {
		mw.selectedfile.SetText("") //通過重定向變量設置TextEdit的Text
		_=writeMessage(mw,"Error : File Open",record) //寫入記錄
		return
	} else if !ok {
		mw.selectedfile.SetText("") //通過重定向變量設置TextEdit的Text
		_=writeMessage(mw,"cancel",record) //寫入記錄
		return
	}
	s := fmt.Sprintf("Select : %s", dlg.FilePath)
	_=writeMessage(mw,s,record) //寫入記錄
	mw.selectedfile.SetText(dlg.FilePath) //通過重定向變量設置TextEdit的Text
}

func (mw *MyMainWindow) uploadFile(){
	filename := mw.selectedfile.Text() //上傳的文件
	record := getMessage(mw) //獲取記錄

	fmt.Printf("filename: %+v\n",filename)
	if len(filename) == 0{
		fmt.Println("select a file")
		_=writeMessage(mw,"請選擇文件",record)
		return
	}


	//-----------------------------------------------
	//模擬表單上傳文件到服務器接口
	bodyBuf := &bytes.Buffer{} //使用二進制緩衝
	bodyBuf.Reset() //重置緩衝
	bodyWriter := multipart.NewWriter(bodyBuf) //創建表單

	//加入token
	curtime := time.Now().Unix()
	h := md5.New()
	io.WriteString(h,strconv.FormatInt(curtime,10))
	token := fmt.Sprintf("%x",h.Sum(nil)) //生成token
	//模擬表單數據
	_ = bodyWriter.WriteField("token", token)
	_ = bodyWriter.WriteField("username", "awef")

	//關鍵的一步操作 ,模擬上傳文件
	//獲取上傳文件名稱
	filenamesplit := strings.Split(filename,"\\")  //使用 \ 分隔符,將字符串切片
	filenamesplit = filenamesplit[len(filenamesplit)-1:] //獲取切片最後一個
	newname := strings.Join(filenamesplit,"")  //切片轉換爲字符串
	fileWriter, err := bodyWriter.CreateFormFile("files", newname) //表單創建一個文件參數
	if err != nil {
		fmt.Println("error writing to buffer")
		_=writeMessage(mw,"error writing to buffer",record)
		return
	}

	//打開文件句柄操作
	fh, err := os.Open(filename)
	if err != nil {
		fmt.Println("error opening file")
		_=writeMessage(mw,"error opening file",record)
		return
	}
	defer fh.Close()

	//iocopy ,複製源文件到 模擬表單的文件裏
	_, err = io.Copy(fileWriter, fh) //fileWriter目標文件 ,fh源文件 ,複製源文件到目標文件
	if err != nil {
		_=writeMessage(mw,"error io.Copy",record)
		return
	}

	contentType := bodyWriter.FormDataContentType() //獲取表單類型
	fmt.Printf("contentType: %+v\n",contentType)
	bodyWriter.Close()

	//resp, err := http.Post(targetUrl, contentType, bodyBuf)
	resp, err := UploadRequest(contentType,bodyBuf)
	if err != nil{
		fmt.Printf("UploadApi err: %+v\n",err)
		_=writeMessage(mw,filename+" 上傳失敗",record)
		return
	}
	fmt.Printf("body : %+v\n",resp)
	//var results string
	// 使用gjson 獲取返回結果的
	coderesult := gjson.Get(resp, "code")
	code := coderesult.String() //斷言轉換成字符串
	if code != "1"{
		_=writeMessage(mw,filename+" 上傳失敗",record)
		return
	}
	record=writeMessage(mw,filename+" 上傳成功",record)

	result := gjson.Get(resp, "data.0.0.url")
	fmt.Printf("result: %+v\n",result)
	resultstring := result.String()//斷言轉換成字符串
	_=writeMessage(mw,resultstring,record)

	return
}

//消息記錄改變事件
func (mw *MyMainWindow) lb_CurrentIndexChanged() {
	fmt.Printf("mw.message.CurrentIndex(): ",mw.message.CurrentIndex())
	fmt.Println()
	return
}
//消息記錄點擊事件
func (mw *MyMainWindow) lb_ItemActivated() {
	fmt.Println("mw.message.CurrentIndex(): ",mw.message.CurrentIndex())
	fmt.Println();
	fmt.Printf("mw.model.items: %+v ",mw.model.items)
	fmt.Println();

	index := mw.message.CurrentIndex()
	imagename := mw.model.items[index].Name //獲取當前選中名稱
	image := mw.model.items[index].Value //獲取當前選中值
	is_http := strings.Index(image,"http") //查找字符串位置
	is_ie := strings.Index(imagename,"默認瀏覽器") //查找字符串位置
	if is_http != -1{ // -1 是找不到
		//walk.MsgBox(mw, "Value", value, walk.MsgBoxIconInformation) //提示框
		fmt.Printf("image : %+v ",image)
		fmt.Println();

		if is_ie != -1{
			openImageExplorer(image) ////使用默認瀏覽器打開圖片
		}else{
			openImageWebview(mw,image) ////使用webview打開圖片
		}
	}
	return
}

//使用默認瀏覽器打開圖片
func openImageExplorer(image string){
	cmd := exec.Command("explorer", image)
	err := cmd.Start()
	if err != nil {
		fmt.Println(err)
	}
	return
}

//創建html,使用webview打開圖片
func openImageWebview(mw *MyMainWindow,image string){
	go func() {
		h := md5.New()
		io.WriteString(h,image)
		htmlname := fmt.Sprintf("%x",h.Sum(nil)) //生成html名稱
		userFile := ""+htmlname+".html"
		fileall := getCurrentDirectory() + "/"+userFile //html的絕對路徑

		fout, err := os.Create(fileall) //創建html文件
		defer fout.Close()
		if err != nil {
			fmt.Println(err)
			return
		}

		html := `<!DOCTYPE html><html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /><title>圖片展示</title></head><body><a href="%s" target="_self">%s</a><br><img src="%s" alt=""></body></html>`
		html = fmt.Sprintf(html,image,image,image)  //插入圖片
		fout.WriteString(html) //把字符串寫入html

		mw.wv.SetURL("file:///" + getCurrentDirectory() + "/"+userFile)
	}()
}
//獲取當前文件路徑
func getCurrentDirectory() string {
	dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
	if err != nil {
		fmt.Println(err.Error())
	}
	return strings.Replace(dir, "\\", "/", -1)
}

//消息記錄每個模型
type MessageItem struct {
	Name  string `json:"name"`
	Value string `json:"value"`
}
//消息記錄模型
type MessageModel struct {
	walk.ListModelBase
	items []MessageItem
}
//新建消息模型
func NewMessageModel() *MessageModel {
	m := &MessageModel{items: []MessageItem{}}
	return m
}
//插入到消息模型中
func appendMessageModel(mw *MyMainWindow,message string) {
	item := MessageItem{
		Name:  message,
		Value: message,
	}
	mw.model.items = append(mw.model.items,item)

}


//獲取記錄
func getMessage(mw *MyMainWindow) []string{
	message := mw.message.Model() //獲取以前的記錄
	fmt.Println("message",message)
	record := []string{} //記錄
	if message != nil {
		for _,v := range message.([]string){
			record = append(record,v)  //插入以前的記錄
		}
	}
	return record
}

//寫入記錄
func writeMessage(mw *MyMainWindow,message string,record []string)[]string{
	is_http := strings.Index(message,"http") //查找字符串位置
	message_record := message
	if is_http != -1{ // -1 是找不到
		message_record = "雙擊查看圖片:"+message

		//插入默認瀏覽器打開記錄
		item := MessageItem{
			Name:  "雙擊用默認瀏覽器打開圖片"+message,
			Value: message,
		}
		mw.model.items = append(mw.model.items,item)
		record = append(record,"雙擊用默認瀏覽器打開圖片"+message)  //插入記錄
	}
	record = append(record,message_record)  //插入記錄
	appendMessageModel(mw,message) //插入模型
	mw.message.SetModel(record) //記錄輸出

	return record
}

/*-------------------上傳接口-------------------*/

type OssStruct struct {
	Url string `json:"url"`
}
//上傳請求
func UploadRequest(contenttype string,buff *bytes.Buffer) (string,error) {
	server := viper.GetString("common.server.server")
	port := viper.GetString("common.server.port")
	serverapi := viper.GetString("common.server.uploadapi")

	api := fmt.Sprintf("%s:%s/%s",server,port,serverapi)

	//發送http請求圖靈api  , body是http響應
	var body, resultErrs = HttpRequest(api,contenttype,buff,"POST")
	if resultErrs != nil {
		fmt.Printf("HttpRequest err: %+v\n",resultErrs)
	}

	return body, nil
}


建立文件夾和文件 goossclient/goossclient.manifest,goossclient.manifest內容如下:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
    <assemblyIdentity version="1.0.0.0" processorArchitecture="*" name="SomeFunkyNameHere" type="win32"/>
    <dependency>
        <dependentAssembly>
            <assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/>
        </dependentAssembly>
    </dependency>
    <application xmlns="urn:schemas-microsoft-com:asm.v3">
        <windowsSettings>
            <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2, PerMonitor</dpiAwareness>
            <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">True</dpiAware>
        </windowsSettings>
    </application>
</assembly>


建立文件夾和文件 goossclient/main.go,main.go內容如下:

package main

import (
	"fmt"

	"github.com/spf13/viper"

	"goossclient/model"

)

func main() {
	viper.AddConfigPath("conf")
	if err := viper.ReadInConfig();err != nil{
		fmt.Println(err)
	}

	model.OpenWindow()
}

初始化模塊

[root@izj6c4jirdug8kh3uo6rdez goossclient]# go mod init goossclient
go: creating new go.mod: module goossclient

打包window客戶端

# 不隱藏cmd 窗口
[root@localhost goossclient]# CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build

# 隱藏cmd 窗口
[root@localhost goossclient]# CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags="-H windowsgui"

# goossclient.exe 就是 windows 客戶端
[root@izj6c4jirdug8kh3uo6rdez goossclient]# ll
total 15956
drwxr-xr-x 2 root root     4096 Dec  5 14:25 conf
-rw-r--r-- 1 root root      277 Dec  7 11:18 go.mod
-rwxr-xr-x 1 root root 16292352 Dec  7 11:18 goossclient.exe
-rw-r--r-- 1 root root      889 Dec  5 14:25 goossclient.manifest
-rw-r--r-- 1 root root    15081 Dec  7 11:18 go.sum
drwxr-xr-x 2 root root     4096 Dec  5 14:25 handler
-rw-r--r-- 1 root root      215 Dec  7 09:39 main.go
drwxr-xr-x 2 root root     4096 Dec  5 14:25 model
-rw-r--r-- 1 root root     1069 Dec  5 14:30 rsrc.syso

把客戶端移動到window系統下運行 goossclient.exe

 

客戶端結束

參考walk:https://github.com/lxn/walk

參考:https://blog.csdn.net/kgjn__/article/details/89288550

參考walk使用:https://studygolang.com/articles/10755

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章