Golang
主页 > 脚本 > Golang >

基于Golang+Vue编写一个手机远程控制电脑的懒人工具

2024-11-18 | 佚名 | 点击:

思路

使用技术

开始操作

前端

封装fetch请求后端api

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

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

 

    // 将查询参数转换为URL编码字符串

    const queryString = Object.keys(params).map(key => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`).join('&');

 

    // 构建完整的请求URL

    let finalUrl = url + (url.includes('?') ? '&' : '?') + queryString;

    finalUrl = finalUrl.includes('http') ? finalUrl : `${baseApi}${finalUrl}`

 

    // 设置请求头部

    const headers = {};

    if (data) headers['Content-Type'] = 'application/json'

 

    // 发起Fetch请求

    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初始化

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

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;

        }

};

按键布局

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

<div id="keyboard">

        <div class="f2">

                <i k="CTRL,A">全选</i><i k="CTRL,C">复制</i><i k="CTRL,V">粘贴</i><i k="CTRL,X">剪切</i><i k="CTRL,Z">撤销</i><i k="CTRL,S">保存</i>

        </div>

        <div class="f2">

                <i k="CTRL,SHIFT">输入法</i><i k="ALT,F4">关闭</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">回车</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>

其他说明

鼠标移动使用websocket实时通信后端, 做了防抖处理, 避免请求太多, 灵敏度高的时候鼠标会有卡顿, 还待优化

后端

初始化项目

1

go mod init dcontrol

下载依赖

1

2

3

4

go get github.com/getlantern/systray

go get github.com/spf13/viper

go get github.com/gorilla/websocket

...

编写代码

1.主函数

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

//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)

 

    // 注册静态资源

    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 处理子路由

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

func HandleApi(w http.ResponseWriter, r *http.Request) {

    w.Header().Set("Access-Control-Allow-Origin", "*")             //允许访问所有域

    w.Header().Add("Access-Control-Allow-Headers", "Content-Type") //header的类型

    w.Header().Set("Content-Type", "application/json")

 

    // 获取请求路径   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任务栏添加应用小图标和菜单

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

func GenTaskBarIcon() {

    if runtime.GOOS == "windows" {

        systray.Run(onReady, onExit)

    }

}

 

func onReady() {

    systray.SetIcon(iconData)

    systray.SetTitle("D-Control")

    systray.SetTooltip("D-Control 右键点击打开菜单!")

    menuOpen := systray.AddMenuItem("打开网页", "打开系统网页")

    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.调用user32.dll, 实现模拟键盘输入和鼠标移动

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

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服务监听

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

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)

            }

        }

 

        // 可以选择回送消息给客户端

        err = ws.WriteMessage(messageType, msg)

        if err != nil {

            fmt.Println("Error while writing message:", err)

            break

        }

    }

}

后言

搭配macast开源投屏神器, 躺在床上手机随时投屏视频到电脑上, 手机再遥控电脑音量和简易操作, 美滋滋了

源码地址

源码和程序截图详见github.com/dhjz/dcontrol

页面效果图见appimg目录

原文链接:
相关文章
最新更新