欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

基于Golang+Vue編寫(xiě)一個(gè)手機(jī)遠(yuǎn)程控制電腦的懶人工具

 更新時(shí)間:2024年11月17日 10:43:49   作者:dhj58369046  
這篇文章主要為大家詳細(xì)介紹了如何基于Golang+Vue編寫(xiě)一個(gè)手機(jī)遠(yuǎn)程控制電腦的懶人工具,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解下

前言

躺在床上投屏到電腦的時(shí)候, 調(diào)節(jié)音量和一些簡(jiǎn)單的操作還需要起身操作, 覺(jué)得麻煩, 就開(kāi)發(fā)了這么一個(gè)小工具。

思路

  • Go語(yǔ)言負(fù)責(zé)后端,負(fù)責(zé)模擬鍵盤(pán)輸入和鼠標(biāo)移動(dòng)
  • Vue負(fù)責(zé)頁(yè)面編寫(xiě),調(diào)用后端接口,使用petite-vue單個(gè)頁(yè)面開(kāi)發(fā), 夠輕量
  • go直接調(diào)用user32.dll完成鍵盤(pán)和鼠標(biāo)的操作, 不依賴(lài)三方框架
  • 前端完全基于瀏覽器, 有微信有掃一掃就能直接打開(kāi)控制頁(yè)
  • 按鍵傳輸采用http請(qǐng)求, 鼠標(biāo)移動(dòng)采用websocket

使用技術(shù)

  • 前端:petite-vue、qrcode
  • 后端:go1.20、systray、websocket

開(kāi)始操作

前端

封裝fetch請(qǐng)求后端api

function request(options, temp) {
	let opts = temp
	if (typeof options != 'string') {
			opts = options
	}
	let { url, method = 'GET', params = {}, data = null } = opts || {};
	if (typeof options == 'string') url = options

	// 將查詢(xún)參數(shù)轉(zhuǎn)換為URL編碼字符串
	const queryString = Object.keys(params).map(key => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`).join('&');

	// 構(gòu)建完整的請(qǐng)求URL
	let finalUrl = url + (url.includes('?') ? '&' : '?') + queryString;
	finalUrl = finalUrl.includes('http') ? finalUrl : `${baseApi}${finalUrl}`

	// 設(shè)置請(qǐng)求頭部
	const headers = {};
	if (data) headers['Content-Type'] = 'application/json'

	// 發(fā)起Fetch請(qǐng)求
	return new Promise((resolve, reject) => {
			fetch(finalUrl, { method, headers, body: data ? JSON.stringify(data) : null}).then(res => {
					if (!res.ok) throw new Error(`HTTP error! status: ${response.status}`);
					return res.json();
			}).then(r => resolve(r)).catch(e => reject(e));
	});
}

websocket初始化

websocket = new WebSocket(`ws://${location.host}/ws`);
websocket.onmessage = function(evt) {
		if(evt.data=="reload"){
				window.location.pathname = "/";
				window.location.reload(true);
		}
}; 
websocket.onopen = function() {
		console.log('socket open....');
		document.getElementById('touch').style.display = 'block';
};
websocket.onclose = function() {
		console.log('socket close....');
		document.getElementById('touch').style.display = 'none';
};
let startTime = 0;
function sendData(data, force) {
		const curr = new Date().getTime();
		if (curr - startTime > 60 || force) {
				console.log('socket send....', data);
				websocket.send(data);
				startTime = curr;
		}
};

按鍵布局

<div id="keyboard">
		<div class="f2">
				<i k="CTRL,A">全選</i><i k="CTRL,C">復(fù)制</i><i k="CTRL,V">粘貼</i><i k="CTRL,X">剪切</i><i k="CTRL,Z">撤銷(xiāo)</i><i k="CTRL,S">保存</i>
		</div>
		<div class="f2">
				<i k="CTRL,SHIFT">輸入法</i><i k="ALT,F4">關(guān)閉</i><i k="WIN,D">桌面</i><i k="MEDIA_PREV_TRACK">上曲</i><i k="MEDIA_NEXT_TRACK">下曲</i>
				<i k="MEDIA_PLAY_PAUSE">播放</i><i k="VOLUME_DOWN">音量-</i><i k="VOLUME_UP">音量+</i><i k="VOLUME_MUTE">靜音</i>
		</div>
		<div class="f1"><i>ESC</i><i>F1</i><i>F2</i><i>F3</i><i>F4</i><i>F5</i><i>F6</i><i>F7</i><i>F8</i><i>F9</i><i>F10</i><i>F11</i><i>F12</i></div>
		<div><i k="OEM_3">`</i><i>1</i><i>2</i><i>3</i><i>4</i><i>5</i><i>6</i><i>7</i><i>8</i><i>9</i><i>0</i><i>BACK</i></div>
		<div><i>TAB</i><i>Q</i><i>W</i><i>E</i><i>R</i><i>T</i><i>Y</i><i>U</i><i>I</i><i>O</i><i>P</i></div>
		<div><i k="CAPITAL">CAPS</i><i>A</i><i>S</i><i>D</i><i>F</i><i>G</i><i>H</i><i>J</i><i>K</i><i>L</i><i k="ENTER">回車(chē)</i></div>
		<div><i>SHFT</i><i>Z</i><i>X</i><i>C</i><i>V</i><i>B</i><i>N</i><i>M</i><i k="HOME">HM</i><i k="UP">↑</i><i k="END">ED</i></div>
		<div>
				<i k="OEM_COMMA">,</i><i k="OEM_PERIOD">.</i><i k="OEM_2">/ </i><i k="OEM_4"> { </i><i k="OEM_6"> } </i><i k="SEMICOLON"> ; </i>
				<i k="OEM_7"> ' </i><i k="OEM_MINU"> - </i><i k="OEM_PLUS"> + </i><i k="LEFT">←</i><i k="DOWN">↓</i><i k="RIGHT">→</i>
		</div>
		<div><span onclick="toggle('#keyboard')">隱藏</span><i style="flex: 1; line-height: 46px; margin-left: 10px;">SPACE</i></div>
</div>

其他說(shuō)明

鼠標(biāo)移動(dòng)使用websocket實(shí)時(shí)通信后端, 做了防抖處理, 避免請(qǐng)求太多, 靈敏度高的時(shí)候鼠標(biāo)會(huì)有卡頓, 還待優(yōu)化

后端

初始化項(xiàng)目

go mod init dcontrol

下載依賴(lài)

go get github.com/getlantern/systray
go get github.com/spf13/viper
go get github.com/gorilla/websocket
...

編寫(xiě)代碼

1.主函數(shù)

  • 通過(guò)go:embed 指定靜態(tài)資源目錄, 可以直接將前端打包的資源封裝入exe中
  • http.HandleFunc 將api前綴交給函數(shù)處理, 在函數(shù)里面具體處理子路由
  • 配置文件通過(guò)config.yml加載, 可以指定快捷應(yīng)用和啟動(dòng)端口
//go:embed webapp
var f embed.FS

func main() {

	port := flag.Int("p", 0, "server port")
	base.RunPort = *port
	filePath := flag.String("f", "./config.yml", "server config file")
	// dir := flag.String("d", "./webapp", "server static dir")
	flag.Parse()
	//1.加載配置
	setting.Init(*filePath)
	base.RunPort = setting.Conf.Port
	if *port != 0 {
		base.RunPort = *port
	}
	addr := fmt.Sprintf(":%d", base.RunPort)

	http.HandleFunc("/control-api/monitor/", monitor.HandleApi)
	http.HandleFunc("/ws", ws.ServeWs)

	// 注冊(cè)靜態(tài)資源
	st, _ := fs.Sub(f, "webapp")
	http.Handle("/", http.StripPrefix("/", http.FileServer(http.FS(st))))

	err := http.ListenAndServe(addr, nil)
	if err != nil {
		fmt.Println("start http error: ", err)
	}
	fmt.Println("start http success ", base.RunPort)
}

2.HandleApi 處理子路由

func HandleApi(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Access-Control-Allow-Origin", "*")             //允許訪(fǎng)問(wèn)所有域
	w.Header().Add("Access-Control-Allow-Headers", "Content-Type") //header的類(lèi)型
	w.Header().Set("Content-Type", "application/json")

	// 獲取請(qǐng)求路徑   strings.HasSuffix
	path := r.URL.Path
	fmt.Println("redis HandleApi path:", path)

	switch {
	case strings.Contains(path, "/getKeyMap"):
		getKeyMap(w, r)
	case strings.Contains(path, "/getIp"):
		getIp(w, r)
	case strings.Contains(path, "/getApps"):
		getApps(w, r)
	case strings.Contains(path, "/sendkey"):
		sendkey(w, r)
	case strings.Contains(path, "/open"):
		open(w, r)
	default:
		http.NotFound(w, r)
	}
}

3.windows任務(wù)欄添加應(yīng)用小圖標(biāo)和菜單

func GenTaskBarIcon() {
	if runtime.GOOS == "windows" {
		systray.Run(onReady, onExit)
	}
}

func onReady() {
	systray.SetIcon(iconData)
	systray.SetTitle("D-Control")
	systray.SetTooltip("D-Control 右鍵點(diǎn)擊打開(kāi)菜單!")
	menuOpen := systray.AddMenuItem("打開(kāi)網(wǎng)頁(yè)", "打開(kāi)系統(tǒng)網(wǎng)頁(yè)")
	systray.AddSeparator()
	menuQuit := systray.AddMenuItem("退出", "退出程序")

	go func() {
		for {
			select {
			case <-menuOpen.ClickedCh:
				OpenBrowser(fmt.Sprintf("http://localhost:%d/", base.RunPort))
			case <-menuQuit.ClickedCh:
				systray.Quit()
				os.Exit(0)
			}
		}
	}()

}

func onExit() {}

4.調(diào)用user32.dll, 實(shí)現(xiàn)模擬鍵盤(pán)輸入和鼠標(biāo)移動(dòng)

var dll = syscall.NewLazyDLL("user32.dll")
var procKeyBd = dll.NewProc("keybd_event")
var procSetCursorPos = dll.NewProc("SetCursorPos")
var procGetCursorPos = dll.NewProc("GetCursorPos")
var procMouseEvent = dll.NewProc("mouse_event")

func SetMouse(x int, y int, isDiff bool) {
	if isDiff {
		procGetCursorPos.Call(uintptr(unsafe.Pointer(&CursorPos)))
		fmt.Println("cursorPos: ", CursorPos.X, CursorPos.Y)
		procSetCursorPos.Call(uintptr(CursorPos.X+int32(x)), uintptr(CursorPos.Y+int32(y)))
	} else {
		procSetCursorPos.Call(uintptr(int32(x)), uintptr(int32(y)))
	}
}

func ClickMouse(str string) {
	if str == "L" {
		procMouseEvent.Call(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0)
		time.Sleep(50 * time.Millisecond) // 短暫延遲
		procMouseEvent.Call(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0)
	} else if str == "R" {
		procMouseEvent.Call(MOUSEEVENTF_RIGHTDOWN, 0, 0, 0, 0)
		time.Sleep(50 * time.Millisecond) // 短暫延遲
		procMouseEvent.Call(MOUSEEVENTF_RIGHTUP, 0, 0, 0, 0)
	} else if str == "M" {
		procMouseEvent.Call(MOUSEEVENTF_MIDDLEDOWN, 0, 0, 0, 0)
		time.Sleep(50 * time.Millisecond) // 短暫延遲
		procMouseEvent.Call(MOUSEEVENTF_MIDDLEUP, 0, 0, 0, 0)
	}
}

func downKey(key int) {
	flag := 0
	if key < 0xFFF { // Detect if the key code is virtual or no
		flag |= _KEYEVENTF_SCANCODE
	} else {
		key -= 0xFFF
	}
	vkey := key + 0x80
	procKeyBd.Call(uintptr(key), uintptr(vkey), uintptr(flag), 0)
}

func upKey(key int) {
	flag := _KEYEVENTF_KEYUP
	if key < 0xFFF {
		flag |= _KEYEVENTF_SCANCODE
	} else {
		key -= 0xFFF
	}
	vkey := key + 0x80
	procKeyBd.Call(uintptr(key), uintptr(vkey), uintptr(flag), 0)
}

// 按鍵映射Map
var KeyMap = map[string]int{
	"SHIFT":               0x10 + 0xFFF,
	"CTRL":                0x11 + 0xFFF,
	"ALT":                 0x12 + 0xFFF,
	"LSHIFT":              0xA0 + 0xFFF,
	"RSHIFT":              0xA1 + 0xFFF,
	"LCONTROL":            0xA2 + 0xFFF,
	"RCONTROL":            0xA3 + 0xFFF,
	"WIN":                 0x5B + 0xFFF,
	...
}

5.websocket服務(wù)監(jiān)聽(tīng)

func ServeWs(w http.ResponseWriter, r *http.Request) {
	ws, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		fmt.Println("upgrade:", err)
		return
	}
	fmt.Println("ServeWs connected......")

	defer ws.Close()

	for {
		// 讀取消息
		messageType, msg, err := ws.ReadMessage()
		if err != nil {
			fmt.Println("Error while reading message:", err)
			break
		}

		// 打印接收到的消息
		fmt.Printf("ws Received: %s\n", msg)
		wsdata := string(msg)
		if wsdata == "pos,click" {
			// go keys.RunKeys(keys.KeyMap["LBUTTON"])
			keys.ClickMouse("L")
		} else if wsdata == "pos,longclick" {
			keys.ClickMouse("R")
		} else if strings.HasPrefix(wsdata, "pos,start") {
			parts := strings.Split(wsdata, ",")
			if len(parts) == 4 {
				fx, _ := strconv.ParseFloat(parts[2], 64)
				fy, _ := strconv.ParseFloat(parts[3], 64)
				keys.SetMouse(int(fx), int(fy), true)
			}
		}

		// 可以選擇回送消息給客戶(hù)端
		err = ws.WriteMessage(messageType, msg)
		if err != nil {
			fmt.Println("Error while writing message:", err)
			break
		}
	}
}

后言

搭配macast開(kāi)源投屏神器, 躺在床上手機(jī)隨時(shí)投屏視頻到電腦上, 手機(jī)再遙控電腦音量和簡(jiǎn)易操作, 美滋滋了

源碼地址

源碼和程序截圖詳見(jiàn)github.com/dhjz/dcontrol

頁(yè)面效果圖見(jiàn)appimg目錄

以上就是基于Golang+Vue編寫(xiě)一個(gè)手機(jī)遠(yuǎn)程控制電腦的懶人工具的詳細(xì)內(nèi)容,更多關(guān)于Go Vue手機(jī)遠(yuǎn)程控制電腦的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • go強(qiáng)制類(lèi)型轉(zhuǎn)換type(a)以及范圍引起的數(shù)據(jù)差異

    go強(qiáng)制類(lèi)型轉(zhuǎn)換type(a)以及范圍引起的數(shù)據(jù)差異

    這篇文章主要為大家介紹了go強(qiáng)制類(lèi)型轉(zhuǎn)換type(a)以及范圍引起的數(shù)據(jù)差異,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-10-10
  • GO中GORM 使用教程

    GO中GORM 使用教程

    GORM是Go語(yǔ)言的ORM庫(kù),它將關(guān)系型數(shù)據(jù)庫(kù)表與Go語(yǔ)言結(jié)構(gòu)體映射,提供便捷的數(shù)據(jù)庫(kù)操作,本文就來(lái)介紹有下GO中GORM 使用教程,具有一定的參考價(jià)值,感興趣的可以了解一下
    2025-02-02
  • gtoken替換jwt實(shí)現(xiàn)sso登錄的問(wèn)題小結(jié)

    gtoken替換jwt實(shí)現(xiàn)sso登錄的問(wèn)題小結(jié)

    這篇文章主要介紹了gtoken替換jwt實(shí)現(xiàn)sso登錄,主要介紹了替換jwt的原因分析及gtoken的優(yōu)勢(shì),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-05-05
  • Go語(yǔ)言字符串高效拼接的實(shí)現(xiàn)

    Go語(yǔ)言字符串高效拼接的實(shí)現(xiàn)

    這篇文章主要介紹了Go語(yǔ)言字符串高效拼接的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-02-02
  • 三種Golang數(shù)組拷貝方式及性能分析詳解

    三種Golang數(shù)組拷貝方式及性能分析詳解

    在Go語(yǔ)言中,我們可以使用for、append()和copy()進(jìn)行數(shù)組拷貝。這篇文章主要為大家詳細(xì)介紹一下這三種方式的具體實(shí)現(xiàn)與性能分析,需要的可以參考一下
    2022-08-08
  • golang容易導(dǎo)致內(nèi)存泄漏的6種情況匯總

    golang容易導(dǎo)致內(nèi)存泄漏的6種情況匯總

    內(nèi)存泄漏是我們?cè)谏a(chǎn)環(huán)境中必須面臨的問(wèn)題,下面這篇文章主要給大家介紹了關(guān)于golang容易導(dǎo)致內(nèi)存泄漏的6種情況,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2023-01-01
  • Go語(yǔ)言學(xué)習(xí)之WaitGroup用法詳解

    Go語(yǔ)言學(xué)習(xí)之WaitGroup用法詳解

    Go語(yǔ)言中的?WaitGroup?和?Java?中的?CyclicBarrier、CountDownLatch?非常類(lèi)似。本文將詳細(xì)為大家講講WaitGroup的用法,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2022-06-06
  • 如何判斷Golang接口是否實(shí)現(xiàn)的操作

    如何判斷Golang接口是否實(shí)現(xiàn)的操作

    這篇文章主要介紹了如何判斷Golang接口是否實(shí)現(xiàn)的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2020-12-12
  • Go?io/fs.FileMode文件系統(tǒng)基本操作和權(quán)限管理深入理解

    Go?io/fs.FileMode文件系統(tǒng)基本操作和權(quán)限管理深入理解

    這篇文章主要為大家介紹了Go?io/fs.FileMode文件系統(tǒng)基本操作和權(quán)限管理深入理解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2024-01-01
  • GO實(shí)現(xiàn)協(xié)程池管理的方法

    GO實(shí)現(xiàn)協(xié)程池管理的方法

    這篇文章給大家介紹GO實(shí)現(xiàn)協(xié)程池管理的方法,分別使用channel實(shí)現(xiàn)協(xié)程池和消費(fèi)者模式實(shí)現(xiàn)協(xié)程池,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友參考下吧
    2021-07-07

最新評(píng)論