首页 > 其他分享 >robotgo以及gohook

robotgo以及gohook

时间:2024-05-30 18:01:10浏览次数:28  
标签:09 gohook 05 fmt 以及 2024 hook robotgo

先安装mingw64 #https://sourceforge.net/projects/mingw-w64/files/mingw-w64/mingw-w64-release/

安装gcc
go env -w CGO_ENABLED=1 #启用cgo

一、gohook

gohook是在代码执行的特定地点,自定义行为;核心是基于GO的反射,

主要功能:

  • 反射机制:使用reflect包,在运行时获取和操作函数信息,实现动态拦截的替换
  • hook注册和解注册:动态插拔,支持多hook,顺序执行

场景:

  • 日志跟踪和测试,在关键函数前后添加hook用于记录和调试
  • 错误处理,对于可能出现错误的函数使用,避免崩溃
  • 性能优化:测量函数执行时间,用于性能优化

1.1、基础用法

package main

import (
	"fmt"
	// "github.com/go-vgo/robotgo"
	hook "github.com/robotn/gohook"
)

func main() {
	//add()
	//low()
	//event()
	test()
}

func add() {
	//注册两个hook,捕获到"w"则打印,捕获到"ctrl+shift+q"就 hook.End
	/*
		hook.Register(arg1, arg2, func(e hook.Event) { //如果捕获到 ctrl+shift+q 就执行println动作
				fmt.Println("ctrl-shift-q")
				hook.End()
			})

		arg1:记录的事件类型,这里用的常量配置
			HookEnabled  = 1 // iota
			HookDisabled = 2
			KeyDown = 3 //键盘动作
			KeyHold = 4
			KeyUp   = 5
			MouseUp    = 6 //鼠标动作
			MouseHold  = 7
			MouseDown  = 8
			MouseMove  = 9
			MouseDrag  = 10
			MouseWheel = 11
			
			FakeEvent = 12
		arg2:事件的值,比如KeyDown为键盘按下,按了“w”键,则arg2为“w”

		func(e hook.Event):为回调函数,捕获到事件以及事件对应的动作后,执行的操作,入参为hook.Event 类型,会把捕获的内容存放到e 值中

	*/

	fmt.Println("--- Please press ctrl + shift + q to stop hook ---")
	hook.Register(hook.KeyDown, []string{"q", "ctrl", "shift"}, func(e hook.Event) { //如果捕获到 ctrl+shift+q 就执行println动作
		fmt.Println("ctrl-shift-q")
		hook.End()
	})

	fmt.Println("--- Please press w---")
	hook.Register(hook.KeyDown, []string{"w"}, func(e hook.Event) { //如果捕获到
		fmt.Println("w")
	})
	
	s := hook.Start() //返回一个chan Event类型
	<-hook.Process(s) //hook.Process内置有一个循环,去捕获请求,并判断是否要执行hook
}

func low() {
	//循环去打印捕获到的内容,这里没有注册 hook
	/*
				type Event struct {
					Kind     uint8 `json:"id"`
					When     time.Time
					Mask     uint16 `json:"mask"`
					Reserved uint16 `json:"reserved"`

					Keycode uint16 `json:"keycode"`  //键盘动作
					Rawcode uint16 `json:"rawcode"`
					Keychar rune   `json:"keychar"`

					Button uint16 `json:"button"`
					Clicks uint16 `json:"clicks"`

					X int16 `json:"x"` //鼠标动作
					Y int16 `json:"y"`

					Amount    uint16 `json:"amount"`
					Rotation  int32  `json:"rotation"`
					Direction uint8  `json:"direction"`
				}
			按键一次有三个动作,RawCode和keychar有区别,比如M和m的rawcode都是77,但是"m"keychar为109,"M"eychar为77
		hook:  2024-05-08 15:30:08.9114509 +0800 CST m=+6.910321501 - Event: {Kind: KeyHold, Rawcode: 81, Keychar: 65535}
		hook:  2024-05-08 15:30:08.9114509 +0800 CST m=+6.910321501 - Event: {Kind: KeyDown, Rawcode: 81, Keychar: 113}
		hook:  2024-05-08 15:30:08.9750345 +0800 CST m=+6.973905101 - Event: {Kind: KeyUp, Rawcode: 81, Keychar: 65535}
		
	*/
	
	evChan := hook.Start()
	defer hook.End()

	for ev := range evChan {
		fmt.Println("hook: ", ev)
		fmt.Println("hook: [KIND]", ev.Kind)
	}

}

func event() {
	//捕获event,依次操作才可以看到效果
	ok := hook.AddEvents("q", "ctrl", "shift")
	if ok {
		fmt.Println("add events...")
	}

	keve := hook.AddEvent("k") //字母k
	if keve {
		fmt.Println("you press... ", "k")
	}

	mleft := hook.AddEvent("mleft") //鼠标左键 ,mouse arguments: mleft, center, mright, wheelDown, wheelUp, wheelLeft, wheelRight.
	if mleft {
		fmt.Println("you press... ", "mouse left button")
	}
}

func test() {
	fmt.Printf("%d\n", 'Q') //81
	fmt.Printf("%c\n", 81)  //Q
}

1.2、记录键盘动作

package main

import (
	"fmt"
	hook "github.com/robotn/gohook"
)

var Unomal_key = map[uint16]string{ //特殊字符需要单独处理
	8:   "[Back]",
	9:   "[Tab]",
	10:  "[Shift]",
	13:  "[Enter]",
	17:  "[Ctrl]",
	18:  "[Alt]",
	20:  "[CAPS LOCK]", //CAPS LOCK
	27:  "[Esc]",
	32:  "[SPACE]", //SPACE
	33:  "[PageUp]",
	34:  "[PageDown]",
	35:  "[End]",
	36:  "[Home]",
	37:  "[Left]",
	38:  "[Up]",
	39:  "[Right]",
	40:  "[Down]",
	41:  "[Select]",
	42:  "[Print]",
	43:  "[Execute]",
	44:  "[PrintScreen]",
	45:  "[Insert]",
	46:  "[Delete]",
	47:  "[Help]",
	91:  "[Windows]",
	92:  "[Windows]",
	93:  "[Applications]",
	95:  "[Sleep]",
	108: "[Separator]",
	111: "[Divide]",
	112: "[F1]",
	113: "[F2]",
	114: "[F3]",
	115: "[F4]",
	116: "[F5]",
	117: "[F6]",
	118: "[F7]",
	119: "[F8]",
	120: "[F9]",
	121: "[F10]",
	122: "[F11]",
	123: "[F12]",
	144: "[NumLock]",
	145: "[ScrollLock]",
	160: "[LShift]", //LShift
	161: "[RShift]", //RShift
	162: "[Ctrl]",
	163: "[Ctrl]",
	164: "[Alt]", //LeftMenu
	165: "[RightMenu]",
}

func ListenKeylogger() {
	fmt.Println("--- Please press ctrl + shift + q to stop hook ---")
	hook.Register(hook.KeyDown, []string{"q", "ctrl", "shift"}, func(e hook.Event) { //如果捕获到 ctrl+shift+q 就执行println动作
		fmt.Println("ctrl-shift-q")
		hook.End()
	})

	hook.Register(hook.KeyDown, []string{}, func(e hook.Event) { //如果捕获到,这样写部分字符捕获不到,比如F1-F12,windows等键
		tmpstr := ""
		if v, ok := Unomal_key[e.Rawcode]; ok {
			tmpstr = v
		} else {
			tmpstr = fmt.Sprintf("%c", e.Keychar)
		}
		fmt.Printf("%v\t:%s\n", e, tmpstr)
	})
	hook.Register(hook.KeyHold, []string{}, func(e hook.Event) {
		tmpstr := ""
		if v, ok := Unomal_key[e.Rawcode]; ok {
			tmpstr = v
			fmt.Printf("%v\t:%s\n", e, tmpstr)
		} else {
			if e.Keychar < 65535 {
				tmpstr = fmt.Sprintf("%c", e.Keychar)
				fmt.Printf("%v\t:%s\n", e, tmpstr)
			}
		}
	})
	
	s := hook.Start() //返回一个chan Event类型
	<-hook.Process(s) //hook.Process内置有一个循环,去捕获请求,并判断是否要执行hook
}

func ListenKeylogger2() {
	evChan := hook.Start()
	defer hook.End()
	
	for e := range evChan {

		tmpstr := ""
		if v, ok := Unomal_key[e.Rawcode]; ok {
			tmpstr = v
			if e.Kind == hook.KeyHold {
				fmt.Printf("%v\t:%s\n", e, tmpstr)
			}
		} else {
			if e.Kind == hook.KeyDown {
				tmpstr = fmt.Sprintf("%c", e.Keychar)
				fmt.Printf("%v\t:%s\n", e, tmpstr)
				continue
			}
		}
	}
}

func main() {
	ListenKeylogger()
	//F*,insert,delete,win,ctrl,alt,shift,CAPS lock,
	//ListenKeylogger2()
}  


/*
	F1按键一次会有如下动作,ListenKeylogger2,这里针对F1这种场景,只提取 KeyHold一个即可,对于1-9,a-z,A-Z使用keychar,对于F1-F12得用Rawcode
2024-05-08 18:25:22.8228022 +0800 CST m=+1.697774101 - Event: {Kind: KeyHold, Rawcode: 112, Keychar: 65535}     :[F1]
2024-05-08 18:25:22.9468061 +0800 CST m=+1.821778001 - Event: {Kind: KeyUp, Rawcode: 112, Keychar: 65535}       :[F1]
	正常按一个英文字符的动作
hook:  2024-05-08 17:52:21.506948 +0800 CST m=+2.944139301 - Event: {Kind: KeyHold, Rawcode: 65, Keychar: 65535}
hook:  2024-05-08 17:52:21.506948 +0800 CST m=+2.944139301 - Event: {Kind: KeyDown, Rawcode: 65, Keychar: 97}
hook:  2024-05-08 17:52:21.5702199 +0800 CST m=+3.007411101 - Event: {Kind: KeyUp, Rawcode: 65, Keychar: 65535}
*/


1.3、优化后

package main

import (
	"fmt"
	hook "github.com/robotn/gohook"
	"os"
	"strings"
	"time"
)

var Unomal_key = map[uint16]string{ //特殊字符需要单独处理
	8:   "[Back]",
	9:   "[Tab]",
	10:  "[Shift]",
	13:  "[Enter]",
	17:  "[Ctrl]",
	18:  "[Alt]",
	20:  "[CAPS LOCK]", //CAPS LOCK
	27:  "[Esc]",
	32:  "[SPACE]", //SPACE
	33:  "[PageUp]",
	34:  "[PageDown]",
	35:  "[End]",
	36:  "[Home]",
	37:  "[Left]",
	38:  "[Up]",
	39:  "[Right]",
	40:  "[Down]",
	41:  "[Select]",
	42:  "[Print]",
	43:  "[Execute]",
	44:  "[PrintScreen]",
	45:  "[Insert]",
	46:  "[Delete]",
	47:  "[Help]",
	91:  "[Windows]",
	92:  "[Windows]",
	93:  "[Applications]",
	95:  "[Sleep]",
	108: "[Separator]",
	111: "[Divide]",
	112: "[F1]",
	113: "[F2]",
	114: "[F3]",
	115: "[F4]",
	116: "[F5]",
	117: "[F6]",
	118: "[F7]",
	119: "[F8]",
	120: "[F9]",
	121: "[F10]",
	122: "[F11]",
	123: "[F12]",
	144: "[NumLock]",
	145: "[ScrollLock]",
	160: "[LShift]", //LShift
	161: "[RShift]", //RShift
	162: "[Ctrl]",
	163: "[Ctrl]",
	164: "[Alt]", //LeftMenu
	165: "[RightMenu]",
}

var printbuffer string //打印字符串
var mouseDrag bool     //全局判断鼠标拖拽是否开始

func printstr(kind string, RKIND uint8, str string, print bool) {
	//fmt.Println("GET:", printbuffer, str, print)
	if print {
		//printbuffer = fmt.Sprintf("%v%v", printbuffer, str)
		fmt.Printf("%v [%v] %v\n", time.Now().Format("2006-01-02 15:04:05"), kind, printbuffer)
		printbuffer = ""
	} else {
		if RKIND == hook.KeyHold {
			//去重
			printbuffer = strings.TrimSuffix(printbuffer, str)
			printbuffer = fmt.Sprintf("%v%v", printbuffer, str)
			//fmt.Printf("====%#v=%#v\n", printbuffer, str)

		} else {
			printbuffer = fmt.Sprintf("%v%v", printbuffer, str)
		}

	}
}

func printMouse(kind, str string, x, y int16, clicks uint16) {
	if str == "鼠标滚轮" {
		fmt.Printf("%v [%v] %v 方向[%v] \n", time.Now().Format("2006-01-02 15:04:05"), kind, str, y)
	} else if str == "鼠标左键拖拽" {
		if mouseDrag {
			return
		} else {
			mouseDrag = true
			fmt.Printf("%v [%v] %v 坐标[%v,%v] \n", time.Now().Format("2006-01-02 15:04:05"), kind, str, x, y)
		}
	} else {
		if mouseDrag && str == "鼠标左键" {
			mouseDrag = false
			fmt.Printf("%v [%v] 鼠标拖拽结束 坐标[%v,%v] \n", time.Now().Format("2006-01-02 15:04:05"), kind, x, y)
		} else {
			fmt.Printf("%v [%v] %v 坐标[%v,%v] 点击次数:%v\n", time.Now().Format("2006-01-02 15:04:05"), kind, str, x, y, clicks)
		}
	}
}

func ListenKeylogger() {
	fmt.Printf("%v [Started to KeyLogger....]\n", time.Now().Format("2006-01-02 15:04:05"))
	evChan := hook.Start()
	
	defer hook.End()
	for e := range evChan {
		tmpstr := ""
		if v, ok := Unomal_key[e.Rawcode]; ok { //对于键盘做记录
			tmpstr = v
			if e.Rawcode == 27 {
				os.Exit(0) //接收到 "ECS"就退出,这种退出方式有问题,会把输入的内容进行重放
				/*
					PS D:\golang\mt\src> .\keyboard.exe
					2024-05-09 09:47:11 [Started to KeyLogger....]
					2024-05-09 09:47:12 jifjsidf[Enter]
					2024-05-09 09:47:14 qjiasd[Enter]
					PS D:\golang\mt\src> jifjsidf
					jifjsidf : 无法将“jifjsidf”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写,如果包括路径,请确保路径正确,然后再试一次。
					所在位置 行:1 字符: 1
					+ jifjsidf
					+ ~~~~~~~~
					    + CategoryInfo          : ObjectNotFound: (jifjsidf:String) [], CommandNotFound
				*/
			}
			//针对特殊字符匹配两个
			if e.Kind == hook.KeyHold {
				printstr("KEYBOARD", e.Kind, tmpstr, false)
			}
			if e.Kind == hook.KeyUp {
				printstr("KEYBOARD", e.Kind, tmpstr, true)
			}

		} else { //非特殊字符,只匹配 hook.KeyDown
			if e.Kind == hook.KeyDown {
				tmpstr = fmt.Sprintf("%c", e.Keychar)
				printstr("KEYBOARD", e.Kind, tmpstr, false)
			}
		}
		//鼠标记录
		/*
			记录的场景:其他场景,暂不记录
				1)滚轮滚动:这里只记录上下方向
				2)左键单击和右键单击
				3)右键选择?
				4)左键拖拽
		*/
		//fmt.Println(e)
		if e.Kind == hook.MouseDown && e.Clicks >= 1 {
			var mouse string
			if e.Button == 2 {
				mouse = "鼠标右键"
			}
			if e.Button == 1 {
				mouse = "鼠标左键"
			}

			printMouse("MOUSE", mouse, e.X, e.Y, e.Clicks)
		}
		if e.Kind == hook.MouseWheel {
			printMouse("MOUSE", "鼠标滚轮", 0, int16(e.Rotation), 0)
		}

		if e.Kind == hook.MouseDrag { //左键拖拽动作,只记录开始和结束坐标
			printMouse("MOUSE", "鼠标左键拖拽", e.X, e.Y, e.Clicks)
		}
	}
}

func main() {
	ListenKeylogger()
	//test()
}

func test() {
	ptime := time.Now().Format("2006-01-02 15:04:05")
	fmt.Println(ptime)

}

/*
鼠标动作:
PS D:\golang\mt\src> .\keyboard.exe #右键一次,Button: 2
2024-05-09 10:11:05.3480506 +0800 CST m=+7.501229501 - Event: {Kind: MouseHold, Button: 2, X: 693, Y: 1195, Clicks: 1}
2024-05-09 10:11:05.4081911 +0800 CST m=+7.561370001 - Event: {Kind: MouseDown, Button: 2, X: 693, Y: 1195, Clicks: 1}
2024-05-09 10:11:05.4081911 +0800 CST m=+7.561370001 - Event: {Kind: MouseUp, Button: 2, X: 693, Y: 1195, Clicks: 1}

PS D:\golang\mt\src> .\keyboard.exe #左键一次,Button: 1,Clicks 在x,y同时的情况下,双击会变成2,三击变成3
2024-05-09 10:12:50.8009638 +0800 CST m=+4.221052001 - Event: {Kind: MouseHold, Button: 1, X: 1125, Y: 1175, Clicks: 1}
2024-05-09 10:12:50.9269416 +0800 CST m=+4.347029701 - Event: {Kind: MouseDown, Button: 1, X: 1125, Y: 1175, Clicks: 1}
2024-05-09 10:12:50.9269416 +0800 CST m=+4.347029701 - Event: {Kind: MouseUp, Button: 1, X: 1125, Y: 1175, Clicks: 1}

## 拖拽动作:Button: 0
2024-05-09 10:17:28.2115929 +0800 CST m=+15.968867701 - Event: {Kind: MouseMove, Button: 0, X: 1977, Y: 362, Clicks: 0}
2024-05-09 10:17:28.4002884 +0800 CST m=+16.157563101 - Event: {Kind: MouseHold, Button: 1, X: 1977, Y: 362, Clicks: 1}
2024-05-09 10:17:28.6504618 +0800 CST m=+16.407736401 - Event: {Kind: MouseDrag, Button: 0, X: 1977, Y: 363, Clicks: 1}
2024-05-09 10:17:28.6504618 +0800 CST m=+16.407736401 - Event: {Kind: MouseDrag, Button: 0, X: 1977, Y: 363, Clicks: 1}
2024-05-09 10:17:28.6504618 +0800 CST m=+16.407736401 - Event: {Kind: MouseDrag, Button: 0, X: 1977, Y: 365, Clicks: 1}
...中间都是 "MouseDrag"
2024-05-09 10:17:30.0938402 +0800 CST m=+17.851114101 - Event: {Kind: MouseDrag, Button: 0, X: 1988, Y: 409, Clicks: 0}
2024-05-09 10:17:30.2828984 +0800 CST m=+18.040172201 - Event: {Kind: MouseDrag, Button: 0, X: 1988, Y: 408, Clicks: 0}
2024-05-09 10:17:30.3464025 +0800 CST m=+18.103676201 - Event: {Kind: MouseDown, Button: 1, X: 1988, Y: 408, Clicks: 0}
2024-05-09 10:17:31.605366 +0800 CST m=+19.362639101 - Event: {Kind: MouseMove, Button: 0, X: 1988, Y: 408, Clicks: 0}
2024-05-09 10:17:31.605366 +0800 CST m=+19.362639101 - Event: {Kind: MouseMove, Button: 0, X: 1988, Y: 409, Clicks: 0}

##正常动作是 MouseMove
2024-05-09 10:34:40.3185 +0800 CST m=+2.200004001 - Event: {Kind: MouseMove, Button: 0, X: 1654, Y: 1366, Clicks: 0}
2024-05-09 10:34:40.3185 +0800 CST m=+2.200004001 - Event: {Kind: MouseMove, Button: 0, X: 1650, Y: 1366, Clicks: 0}


##滚轮动作,向上两下 Rotation:-1,向下两下, Rotation:1
2024-05-09 10:35:51.9729837 +0800 CST m=+2.062794701 - Event: {Kind: MouseWheel, Amount: 0, Rotation: -1, Direction: 3}
2024-05-09 10:35:52.7855524 +0800 CST m=+2.875363401 - Event: {Kind: MouseWheel, Amount: 0, Rotation: -1, Direction: 3}
2024-05-09 10:35:53.9752604 +0800 CST m=+4.065071201 - Event: {Kind: MouseWheel, Amount: 0, Rotation: 1, Direction: 3}
2024-05-09 10:35:54.2247366 +0800 CST m=+4.314547401 - Event: {Kind: MouseMove, Button: 0, X: 994, Y: 1211, Clicks: 0}
2024-05-09 10:35:54.4135664 +0800 CST m=+4.503377201 - Event: {Kind: MouseMove, Button: 0, X: 992, Y: 1211, Clicks: 0}
2024-05-09 10:35:54.7895778 +0800 CST m=+4.879388601 - Event: {Kind: MouseWheel, Amount: 0, Rotation: 1, Direction: 3}
2024-05-09 10:35:54.9158717 +0800 CST m=+5.005682401 - Event: {Kind: MouseWheel, Amount: 0, Rotation: 1, Direction: 3}

##滚轮按住一下
*/


测试日志输出如下:
PS D:\golang\mt\src> .\keyboard.exe
2024-05-09 16:07:21 [Started to KeyLogger....]
2024-05-09 16:07:22 [KEYBOARD] 
2024-05-09 16:07:24 [KEYBOARD] [Ctrl][LShift]:T
2024-05-09 16:07:24 [KEYBOARD] 
2024-05-09 16:07:27 [MOUSE] 鼠标左键拖拽 坐标[131,887] 
2024-05-09 16:07:27 [MOUSE] 鼠标拖拽结束 坐标[131,887] 
2024-05-09 16:07:28 [MOUSE] 鼠标左键 坐标[600,1112] 点击次数:1
2024-05-09 16:07:32 [KEYBOARD] jidsjif[Enter]
2024-05-09 16:07:34 [KEYBOARD] women[Enter]
2024-05-09 16:07:36 [KEYBOARD] hello[SPACE]
2024-05-09 16:07:37 [KEYBOARD] world[Enter]
2024-05-09 16:07:40 [KEYBOARD] heloo[SPACE]
2024-05-09 16:07:41 [KEYBOARD] world[Enter]
2024-05-09 16:07:43 [KEYBOARD] ceshi[Enter]
2024-05-09 16:07:45 [KEYBOARD] [Home]
2024-05-09 16:07:46 [KEYBOARD] [End]

二、robotgo

windows api:https://learn.microsoft.com/en-us/uwp/api/

2.1、按键对应

参考链接

	"A-Z a-z 0-9"

	"backspace"
	"delete"
	"enter"
	"tab"
	"esc"
	"escape"
	"up"		Up arrow key
	"down"		Down arrow key
	"right"		Right arrow key
	"left"		Left arrow key
	"home"
	"end"
	"pageup"
	"pagedown"
	
	"f1"
	"f2"
	"f3"
	"f4"
	"f5"
	"f6"
	"f7"
	"f8"
	"f9"
	"f10"
	"f11"
	"f12"
	"f13"
	"f14"
	"f15"
	"f16"
	"f17"
	"f18"
	"f19"
	"f20"
	"f21"
	"f22"
	"f23"
	"f24"

	"cmd"		is the "win" key for windows
	"lcmd"		left command
	"rcmd"		right command
	// "command"
	"alt"
	"lalt"		left alt
	"ralt"		right alt
	"ctrl"
	"lctrl"		left ctrl
	"rctrl"		right ctrl
	"control"
	"shift"
	"lshift"	left shift
	"rshift"	right shift
	// "right_shift"
	"capslock"
	"space"
	"print"
	"printscreen"      // No Mac support
	"insert"
	"menu"				Windows only

	"audio_mute"		Mute the volume
	"audio_vol_down"	Lower the volume
	"audio_vol_up"		Increase the volume
	"audio_play"
	"audio_stop"
	"audio_pause"
	"audio_prev"		Previous Track
	"audio_next"		Next Track
	"audio_rewind"      Linux only
	"audio_forward"     Linux only
	"audio_repeat"      Linux only
	"audio_random"      Linux only


	"num0"
	"num1"
	"num2"
	"num3"
	"num4"
	"num5"
	"num6"
	"num7"
	"num8"
	"num9"
	"num_lock"

	"num."
	"num+"
	"num-"
	"num*"
	"num/"
	"num_clear"
	"num_enter"
	"num_equal"

	// // "numpad_0"		No Linux support
	// "numpad_0"
	// "numpad_1"
	// "numpad_2"
	// "numpad_3"
	// "numpad_4"
	// "numpad_5"
	// "numpad_6"
	// "numpad_7"
	// "numpad_8"
	// "numpad_9"
	// "numpad_lock"

	"lights_mon_up"		 Turn up monitor brightness					No Windows support
	"lights_mon_down"	 Turn down monitor brightness				No Windows support
	"lights_kbd_toggle"	 Toggle keyboard backlight on/off			No Windows support
	"lights_kbd_up"		 Turn up keyboard backlight brightness		No Windows support
	"lights_kbd_down"	 Turn down keyboard backlight brightness	No Windows support

2.2、mouse

func getScreenScale() (scroll int) { //获取屏幕缩放比例
	//这里设置取值范围为50%,75%,100%,125%,150%,175%,200%,225%,250%,275%,300%
	x, y := robotgo.GetScreenSize()
	a, b := robotgo.Location()
	id := robotgo.DisplayID
	robotgo.MilliSleep(100)
	robotgo.Move(x/2, y/2, id)

	m, _ := robotgo.Location()
	robotgo.Move(a, b, id) //返回原始位置
	per := float32(m) * 2 / float32(x)
	switch {
	case per > 2.75:
		scroll = 300
	case per > 2.5:
		scroll = 275
	case per > 2.25:
		scroll = 250
	case per > 2.0:
		scroll = 225
	case per > 1.75:
		scroll = 200
	case per > 1.5:
		scroll = 175
	case per > 1.25:
		scroll = 150
	case per > 1.0:
		scroll = 125
	case per > 0.75:
		scroll = 100
	case per > 0.5:
		scroll = 75
	case per > 0.25:
		scroll = 50
	default:
		fmt.Printf("[ERROR] 获取缩放比例值异常 %v \n", scroll)
	}
	return scroll
}

func getRealMove(m, n int, scroll int) (x, y int) {
	x = (m * 100) / scroll
	y = (n * 100) / scroll
	return
}

func mouse() {
	/*
		注意:windows屏幕缩放会影响鼠标的位置,
	*/
	x, y := getRealMove(100, 100, getScreenScale())
	robotgo.Move(x, y) //直接移动到(100,100) Location的结果为 125,125,使用 getRealMove获取移动到想要的坐标的真实坐标,即可
	
	robotgo.MoveSmooth(100, 100) //鼠标在屏幕上平滑移动
	
	robotgo.MouseSleep = 100
	time.Sleep(1 * time.Second)
	
	//鼠标中键
	robotgo.ScrollDir(10, "up") //Scroll滚动,direct 指令  (x, "up") supported: "up", "down", "left", "right"
	robotgo.ScrollDir(20, "right")
	robotgo.ScrollSmooth(-20, 6, 500) //每次向下20个单位,执行6次,每次间隔500ms,

	robotgo.DragSmooth(10, 10)  //从当前位置,平滑拖拽(按住鼠标左键)到目的位置
	robotgo.Click("wheelRight") //点击
	/* 支持的参数:
	"left":       C.LEFT_BUTTON, //默认,
		"center":     C.CENTER_BUTTON,
		"right":      C.RIGHT_BUTTON,
		"wheelDown":  C.WheelDown,
		"wheelUp":    C.WheelUp,
		"wheelLeft":  C.WheelLeft, //可以用在某些(但并非所有)带第二个滚轮或支持左右滚动的鼠标热键。在某些情况下,必须通过鼠标的自带软件包控制这个功能
		"wheelRight": C.WheelRight,
	*/
	robotgo.Click("left", true)
	robotgo.MoveClick(10, 20, "left")
	fmt.Println(robotgo.Location()) //屏幕缩放 125%,输出为12(10*1.25),25(20*1.25))
}

2.3、keyboard

func keyboard() {
	//1、按住win + r键,输入cmd,打开cmd窗口
	robotgo.KeySleep = 200 //设置默认键盘输入等待时间
		
	robotgo.KeyTap("r", "cmd") //按键操作,写法1
	//k1 := []string{"r", "cmd"} //按键操作,写法2
	//robotgo.KeyTap(k1)
	
	robotgo.TypeStr("cmd") //输入字符
	robotgo.KeyTap("enter")
	
	//2、使用微信截图alt +a ,注意这里按住alt键后要释放,不然alt键会一直处于down的状态,影响使用
	robotgo.KeyToggle("alt", "down")
	robotgo.KeyToggle("a")
	fmt.Println("alt 键被按住了")
	robotgo.KeyToggle("alt", "up") //截屏操作结束后,才会执行下一个动作
	time.Sleep(5 * time.Second)
	fmt.Println("alt 键被释放")
	
	//3、修改粘贴板
	robotgo.WriteAll("Test") //修改粘贴板

	text, err := robotgo.ReadAll()
	if err == nil {
		fmt.Println(text)
	}
}

2.4、screen

func screen() {
	x, y := robotgo.Location()
	fmt.Println("pos: ", x, y)

	color := robotgo.GetPixelColor(100, 200)
	fmt.Println("color---- ", color)

	sx, sy := robotgo.GetScreenSize() //获取屏幕大小
	fmt.Println("get screen size: ", sx, sy)

	bit := robotgo.CaptureScreen(10, 10, 30, 30) //截图
	defer robotgo.FreeBitmap(bit)

	img := robotgo.ToImage(bit)
	imgo.Save("test.png", img)

	num := robotgo.DisplaysNum() //获取显示器个数
	for i := 0; i < num; i++ {
		robotgo.DisplayID = i
		img1 := robotgo.CaptureImg()
		path1 := "save_" + strconv.Itoa(i)
		robotgo.Save(img1, path1+".png")
		robotgo.SaveJpeg(img1, path1+".jpeg", 50)

		img2 := robotgo.CaptureImg(10, 10, 20, 20)
		robotgo.Save(img2, "test_"+strconv.Itoa(i)+".png")
	}
}

2.5、event

这里用的gohook,不知道为啥用的gohook,而不是自己的

func main() {
  add()
  low()
  event()
}

func add() {
  fmt.Println("--- Please press ctrl + shift + q to stop hook ---")
  hook.Register(hook.KeyDown, []string{"q", "ctrl", "shift"}, func(e hook.Event) {
    fmt.Println("ctrl-shift-q")
    hook.End()
  })

  fmt.Println("--- Please press w---")
  hook.Register(hook.KeyDown, []string{"w"}, func(e hook.Event) {
    fmt.Println("w")
  })

  s := hook.Start()
  <-hook.Process(s)
}

func low() {
	evChan := hook.Start()
	defer hook.End()

	for ev := range evChan {
		fmt.Println("hook: ", ev)
	}
}

func event() {
  ok := hook.AddEvents("q", "ctrl", "shift")
  if ok {
    fmt.Println("add events...")
  }

  keve := hook.AddEvent("k")
  if keve {
    fmt.Println("you press... ", "k")
  }

  mleft := hook.AddEvent("mleft")
  if mleft {
    fmt.Println("you press... ", "mouse left button")
  }
}

2.6、windows

func windows() {
	//很多动作在windows上无效,linux暂未测试

	//1、消息提示框
	//abool := robotgo.Alert("test", "robotgo", "ok", "cancel") //弹窗提示
	//if abool {                                                //abool为弹窗选择的是 确定还是取消
	//	fmt.Println("ok@@@ ", "ok")
	//	title := robotgo.GetTitle()
	//	fmt.Println("title@@@ ", title)
	//}

	//2、切换窗口
	currid := robotgo.GetActive() //获取notepadd进程,并切换到noteepad++,输入"hello world"然后切换回来
	fpid, err := robotgo.FindIds("notepad++")
	if err != nil {
		fmt.Println("获取notepadd++ id失败")
	} else {
		fmt.Println("fpid:", fpid)
		robotgo.MilliSleep(500) //这里需要等待一段时间,不然看不到效果
		robotgo.ActivePid(fpid[0])
		robotgo.MilliSleep(500)
		t := robotgo.GetTitle(fpid[0]) //试了没用
		fmt.Println("notepad++ [title]:", t)
		robotgo.MouseDown("left")
		robotgo.TypeStrDelay("hello world", 500)
	}
	robotgo.MilliSleep(500)
	robotgo.SetActive(currid)

	//3、其他
	/*
		更多的windows窗口操作,需要调用windows api去看
	*/
}

三、调用windows

windows api: https://learn.microsoft.com/en-us/windows/win32/apiindex/api-index-portal

syscall包与系统直接通信,不用用到CGO 。 然而,也有不方便的地方,如大多数的API,需要依赖不安全 (unsafe)的内存管理

四、小工具

4.1、截屏小程序

var capPos struct { //保存位置
	x, y, m, n int
}

func capturePix() {
	fmt.Println("--- Please press ctrl + shift + q to stop hook ---")
	hook.Register(hook.KeyDown, []string{"q", "ctrl", "shift"}, func(e hook.Event) { //如果捕获到 ctrl+shift+q 就执行println动作
		fmt.Println("ctrl-shift-q")
		hook.End()
	})
	
	hook.Register(hook.KeyDown, []string{"r", "ctrl"}, func(e hook.Event) { //
		x, y := robotgo.Location()
		color := robotgo.GetPixelColor(x, y)
		fmt.Printf("%v 位置:[%v,%v] 颜色%v\n", time.Now().Format("2006-01-02 15:04:05"), x, y, color)
	})

	s := hook.Start() //返回一个chan Event类型
	<-hook.Process(s) //hook.Process内置有一个循环,去捕获请求,并判断是否要执行hook
}

func screenCut() {
	/* 需求
	1、功能一:根据鼠标位置获取颜色信息,根据ctrl + r 判断是否要执行动作
	2、功能二:截屏
		1)ecs键撤销,重新截屏
		2)ctrl+shift+x开始,ctrl+shift+e结束截屏
		3)默认保存为png格式
	3、ctrl+shift +q 键,全局退出
	*/

	abool := robotgo.Alert("screen cust tool...", "Screenshot or capture color information")
	if !abool { //检测颜色和像素鼠标位置
		capturePix()
		return
	} else {
		fmt.Println("--- Please press ctrl + shift + q to stop hook ---")
		hook.Register(hook.KeyDown, []string{"q", "ctrl", "shift"}, func(e hook.Event) { //如果捕获到 ctrl+shift+q 就执行println动作
			fmt.Println("使用截屏功能,按住ctrl-shift-q 退出,ecs键重新截取")
			hook.End()
		})

		hook.Register(hook.KeyDown, []string{"x", "ctrl", "shift"}, func(e hook.Event) { //开始截屏
			fmt.Printf("[截屏开始],按住ctrl-shift-q 退出,ecs键重新截取\n")
			robotgo.MilliSleep(200)
			capPos.x, capPos.y = robotgo.Location()
			fmt.Printf("[截屏开始] 开始位置:[%v,%v] %#v\n", capPos.x, capPos.y, capPos)
		})

		hook.Register(hook.KeyDown, []string{}, func(e hook.Event) { //捕获到ecs就重新开始
			if _, ok := Unomal_key[e.Rawcode]; ok {
				fmt.Printf("[截屏ECS重新开始],按住ctrl-shift-q 退出,ecs键重新截取\n")
				if e.Rawcode == 27 {
					capPos.x, capPos.y, capPos.m, capPos.n = 0, 0, 0, 0
				}
			}
		})

		hook.Register(hook.KeyDown, []string{"e", "ctrl", "shift"}, func(e hook.Event) { //结束截屏
			capPos.m, capPos.n = robotgo.Location()
			fmt.Printf("[截屏结束] %v 位置:[%v] \n", time.Now().Format("2006-01-02 15:04:05"), capPos)
			cmap := robotgo.CaptureScreen(capPos.x, capPos.y, capPos.m, capPos.n)
			defer robotgo.FreeBitmap(cmap)
			img := robotgo.ToImage(cmap)
			err := imgo.SaveToPNG(fmt.Sprintf("%v.png", time.Now().Format("20060102_150405")), img)
			if err != nil {
				fmt.Println("截屏失败:", err)
			}
		})
		
		s := hook.Start() //返回一个chan Event类型
		<-hook.Process(s) //hook.Process内置有一个循环,去捕获请求,并判断是否要执行hook
	}
}

4.2、腾讯ocr

腾讯的比较坏算,免费

package main

import (
	"bufio"
	"bytes"
	"encoding/json"
	"fmt"
	"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
	"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/errors"
	"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
	ocr "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ocr/v20181119"
	"io"
	"os"
	"strings"
)

func PrettyString(str string) (string, error) {
	var prettyJSON bytes.Buffer
	if err := json.Indent(&prettyJSON, []byte(str), "", " "); err != nil {
		return "", err
	}
	return prettyJSON.String(), nil
}

func test(str string) {
	fmt.Printf("[%v] will removed ...", str)
	err := os.Remove(str)
	if err != nil {
		fmt.Println(err)
	}
}

func main() {
	//1、配置client对象
	credential := common.NewCredential(
		"ak1",
		"sk2", //注意替换
	)
	// 实例化一个client选项,可选的,没有特殊需求可以跳过
	cpf := profile.NewClientProfile()
	cpf.HttpProfile.Endpoint = "ocr.tencentcloudapi.com"
	// 实例化要请求产品的client对象,clientProfile是可选的
	client, _ := ocr.NewClient(credential, "ap-guangzhou", cpf)

	//2、初始化请求
	request := ocr.NewGeneralBasicOCRRequest()
	request.ImageUrl = common.StringPtr("https://ocr-demo-1254418846.cos.ap-guangzhou.myqcloud.com/card/IDCardOCR/IDCardOCR1.jpg")

	// 返回的resp是一个GeneralBasicOCRResponse的实例,与请求对象对应
	response, err := client.GeneralBasicOCR(request)
	if _, ok := err.(*errors.TencentCloudSDKError); ok {
		fmt.Printf("An API error has returned: %s", err)
		return
	}
	if err != nil {
		panic(err)
	}
	// 输出1:输出json格式的字符串回包
	str, err := PrettyString(response.ToJsonString())
	if err != nil {
		fmt.Printf("出错了:%v\n", err)
	}
	fmt.Printf("%s", str)

	//输出2:提取关键内容,内容太多了,很多不需要(借助临时文件,创建fileobj对象,遍历fileobj内容)
	fileobj, err := os.CreateTemp(".", "temp-")
	if err != nil {
		fmt.Println("临时文件创建失败", err)
	} else {
		fmt.Println("临时文件创建成功")
	}

	count, err := fileobj.WriteString(str)
	defer os.RemoveAll(fileobj.Name()) //defer的执行顺序是先进后出,这个执行顺序要在关闭后执行
	defer fileobj.Close()

	fmt.Println("NAME:", fileobj.Name())

	if err != nil {
		fmt.Println("临时文件写入失败", err)
	} else {
		fmt.Println("文件写入:", count)
	}

	//reader := bufio.NewReader(fileobj)
	fobj2, _ := os.Open(fileobj.Name())
	reader := bufio.NewReader(fobj2)
	defer fobj2.Close()

	for {
		line, err := reader.ReadString('\n')
		if err == io.EOF {
			fmt.Println("文件读取完毕")
			break
		}
		if err != nil {
			fmt.Println("文件读取异常", err)
		}
		if strings.Contains(line, "DetectedText") {
			fmt.Println(strings.TrimSpace(line))
		}
	}
}

五、chromedp

Package chromedp is a faster, simpler way to drive browsers supporting the Chrome DevTools Protocol in Go without external dependencies.

The Chrome DevTools Protocol allows for tools to instrument, inspect, debug and profile Chromium, Chrome and other Blink-based browsers. Many existing projects currently use the protocol. The Chrome DevTools uses this protocol and the team maintains its API.

chromedp是go写的(可以通过使用cdp协议驱动chrome)库,cdp协议是驱动浏览器的一种协议,我们打开浏览器的开发者模式其实也是通过cdp协议和浏览器建立了链接。cdp本质上是一种websocket协议

默认chromedp为headless模式,看不到界面输出

  • 自学地址: https://pkg.go.dev/github.com/chromedp/chromedp (版本 0.9.1)
  • cdp介绍: https://chromedevtools.github.io/devtools-protocol/

注意:chromedp比较依赖context包,chromedp 学习比较重要的两个方面一个是context,一个是html元素查找器。

golang查找html元素,可以使用goquery或者htmlquery,htmlquery使用xpath选择器,goquery使用css选择器,但是htmlquery在github上用的并不多,goquery用的比较活跃,xpath相对简单好学,chrome开发者模式,右键“copy selector”对应的是css选择器

xpath完整的语法:https://github.com/antchfx/xpath ;xpath的页面选择器

  • htmlquery - an XPath query package for HTML document
  • xmlquery - an XPath query package for XML document.
  • jsonquery - an XPath query package for JSON document
  • goquery: 基于net/html包以及 CSS Selector library cascadia,类jQuery的特性

5.1、xpath

xpath (摘录自w3cschool)

xpath使用路径表达式在XML文档中进行导航,XPath 使用路径表达式来选取 XML 文档中的节点或者节点集。这些路径表达式和我们在常规的电脑文件系统中看到的表达式非常相似。

1.1.1、xpath节点

在 XPath 中,有七种类型的节点:元素、属性、文本、命名空间、处理指令、注释以及文档(根)节点。XML 文档是被作为节点树来对待的。树的根被称为文档节点或者根节点。

<?xml version="1.0" encoding="ISO-8859-1"?>
	
 <bookstore> #文档节点
  <book>
     <title lang="en">Harry Potter</title> #lang="en" 为属性节点  ,"Harry Potter" 为基本值,基本值及无父或无子的节点
     <author>J K. Rowling</author> #元素节点,"J K. Rowling" 为基本值,基本值及无父或无子的节点
     <year>2005</year>
     <price>29.99</price>
   </book>
</bookstore> 

//book 元素是 title、author、year 以及 price 元素的父
title、author、year 以及 price 元素都是 book 元素的子
title、author、year 以及 price 元素都是同胞
title 元素的先辈是 book 元素和 bookstore 元素,上一级和上两级都是父
bookstore 的后代是 book、title、author、year 以及 price 元素

1.1.2、xpath语法

XPath 使用路径表达式在 XML 文档中选取节点。节点是通过沿着路径或者 step 来选取的。 下面列出了最有用的路径表达式:

表达式 描述
nodename 选取此节点的所有子节点。
/ 从根节点选取。
// 从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置。
. 选取当前节点。
.. 选取当前节点的父节点。
@ 选取属性。

在下面的表格中,我们已列出了一些路径表达式以及表达式的结果:

路径表达式 结果
bookstore 选取 bookstore 元素的所有子节点。
/bookstore 选取根元素 bookstore。注释:假如路径起始于正斜杠( / ),则此路径始终代表到某元素的绝对路径!
bookstore/book 选取属于 bookstore 的子元素的所有 book 元素。
//book 选取所有 book 子元素,而不管它们在文档中的位置。
bookstore//book 选择属于 bookstore 元素的后代的所有 book 元素,而不管它们位于 bookstore 之下的什么位置。
//@lang 选取名为 lang 的所有属性。

提示:如果 XPath 的开头是一个斜线(/)代表这是绝对路径。如果开头是两个斜线(//)表示文件中所有符合模式的元素都会被选出来,即使是处于树中不同的层级也会被选出来。

1.1.3、谓语(Predicates)

谓语用来查找某个特定的节点或者包含某个指定的值的节点。

谓语被嵌在方括号中。

在下面的表格中,我们列出了带有谓语的一些路径表达式,以及表达式的结果:

路径表达式 结果
/bookstore/book[1] 选取属于 bookstore 子元素的第一个 book 元素。
/bookstore/book[last()] 选取属于 bookstore 子元素的最后一个 book 元素。
/bookstore/book[last()-1] 选取属于 bookstore 子元素的倒数第二个 book 元素。
/bookstore/book[position()❤️] 选取最前面的两个属于 bookstore 元素的子元素的 book 元素。
//title[@lang] 选取所有拥有名为 lang 的属性的 title 元素。
//title[@lang='eng'] 选取所有 title 元素,且这些元素拥有值为 eng 的 lang 属性。
/bookstore/book[price>35.00] 选取 bookstore 元素的所有 book 元素,且其中的 price 元素的值须大于 35.00。
/bookstore/book[price>35.00]/title 选取 bookstore 元素中的 book 元素的所有 title 元素,且其中的 price 元素的值须大于 35.00。

1.1.4、选取未知节点

XPath 通配符可用来选取未知的 XML 元素。

通配符 描述
* 匹配任何元素节点。
@* 匹配任何属性节点。
node() 匹配任何类型的节点。

在下面的表格中,我们列出了一些路径表达式,以及这些表达式的结果:

路径表达式 结果
/bookstore/* 选取 bookstore 元素的所有子元素。
//* 选取文档中的所有元素。
//title[@*] 选取所有带有属性的 title 元素。

1.1.5、选取若干路径

通过在路径表达式中使用"|"运算符,可以选取若干个路径。

路径表达式 结果
//book/title | //book/price 选取 book 元素的所有 title 和 price 元素。
//title | //price 选取文档中的所有 title 和 price 元素。
/bookstore/book/title | //price 选取属于 bookstore 元素的 book 元素的所有 title 元素,以及文档中所有的 price 元素。

1.1.6、XPath轴(Axes)

轴可定义相对于当前节点的节点集。

轴名称 结果
ancestor 选取当前节点的所有先辈(父、祖父等)。
ancestor-or-self 选取当前节点的所有先辈(父、祖父等)以及当前节点本身。
attribute 选取当前节点的所有属性。
child 选取当前节点的所有子元素。
descendant 选取当前节点的所有后代元素(子、孙等)。
descendant-or-self 选取当前节点的所有后代元素(子、孙等)以及当前节点本身。
following 选取文档中当前节点的结束标签之后的所有节点。
namespace 选取当前节点的所有命名空间节点。
parent 选取当前节点的父节点。
preceding 选取文档中当前节点的开始标签之前的所有节点。
preceding-sibling 选取当前节点之前的所有同级节点。
self 选取当前节点。

1.1.7、xpath运算符

运算符 描述 实例 返回值
| 计算两个节点集 //book | //cd 返回所有拥有 book 和 cd 元素的节点集
+ 加法 6 + 4 10
- 减法 6 - 4 2
* 乘法 6 * 4 24
div 除法 8 div 4 2
= 等于 price=9.80 如果 price 是 9.80,则返回 true。如果 price 是 9.90,则返回 false。
!= 不等于 price!=9.80 如果 price 是 9.90,则返回 true。如果 price 是 9.80,则返回 false。
< 小于 price<9.80 如果 price 是 9.00,则返回 true。如果 price 是 9.90,则返回 false。
<= 小于或等于 price<=9.80 如果 price 是 9.00,则返回 true。如果 price 是 9.90,则返回 false。
> 大于 price>9.80 如果 price 是 9.90,则返回 true。如果 price 是 9.80,则返回 false。
>= 大于或等于 price>=9.80 如果 price 是 9.90,则返回 true。如果 price 是 9.70,则返回 false。
or price=9.80 or price=9.70 如果 price 是 9.80,则返回 true。如果 price 是 9.50,则返回 false。
and price>9.00 and price<9.90 如果 price 是 9.80,则返回 true。如果 price 是 8.50,则返回 false。
mod 计算除法的余数 5 mod 2 1

5.2、css选择器

学习地址:https://www.w3school.com.cn/cssref/css_selectors.asp

标签:09,gohook,05,fmt,以及,2024,hook,robotgo
From: https://www.cnblogs.com/hmtk123/p/18222968

相关文章

  • 深入探索汇编语言的顶尖级应用领域,包括多核并行处理、物联网和嵌入式系统、高性能计算
    汇编语言初级应用的大纲:1.汇编语言概述介绍汇编语言的基本概念和作用。解释汇编语言与高级语言的区别。简要说明汇编语言的历史和发展。2.汇编语言基础讲解汇编语言的基本语法和结构。介绍汇编语言中的指令、寄存器、内存等概念。解释汇编语言程序的组成部分,如数据段......
  • OpenCv之简单的人脸识别项目(人脸识别页面以及人脸比对页面)
    人脸识别准备三、人脸识别页面1.导入所需的包2.设置窗口2.1定义窗口外观和大小2.2设置窗口背景2.2.1设置背景图片2.2.2创建label控件3.定义两个全局变量4.定义选择并显示图片的函数4.1声明全局变量4.2设置文件选择对话框4.3设置条件语句4.4创建一个标签显示图像5.定义......
  • 数据结构之栈(Java,C语言的实现)以及相关习题巩固
    目录栈概念以及代码实现例题232.用栈实现队列1614.括号的最大嵌套深度234.回文链表1614.括号的最大嵌套深度LCR123.图书整理I206.反转链表402.移掉K位数字844.比较含退格的字符串LCR036.逆波兰表达式求值[面试题03.01.三合一](栈概念以及代码实现栈是仅限于在......
  • 数据结构 顺序表(C语言 与 Java实现)以及部分练习题
    目录数据结构数组(顺序表)特点使用Java实现更高级的数组C语言实现总结优点缺点例题26.删除有序数组中的重复项1.两数之和27.移除元素153.寻找旋转排序数组中的最小值485.最大连续1的个数414.第三大的数2656.K个元素的最大和LCP06.拿硬币2057.值相等的最小索引26.删......
  • Windows镜像ISO下载以及永久使用(亲测有效)
    1、镜像ISO文件下载点击立即下载工具按钮,先下载这个工具双击下载好的工具MediaCreationTool22H2下载时间需要一会儿,下载好ISO文件,关闭窗口即可。2、工具下载下载工具下载地址https://pan.baidu.com/s/1KoQ9lhFdrYNd2dDb7539Ng?pwd=wnbt说明:下载工具......
  • 你知道列存储的定义和优势以及行存储的区别?--数据仓库基本概念
    列存储(ColumnarStorage)是一种数据库存储数据的方式,它将每一列的数据存储在一起,而不是按行存储。这与传统的行存储(RowStorage)相反,后者将一行中的所有数据存储在一起。列存储的定义:列存储数据库或文件系统会将表中的每一列数据分别存储在不同的位置。例如,如果有一个表包含......
  • paddleXOCR c++ vs2022编译以及使用
    PaddleOCR的使用(C++)——Windows编译篇-夕西行-博客园(cnblogs.com) 参考官方的指导地址,按照他的来很全PaddleOCR/deploy/cpp_infer/docs/windows_vs2019_build.mdatmain·PaddlePaddle/PaddleOCR·GitHub1.opencv我这里用的4.4(高版本应该也可以)Releases-OpenC......
  • Node性能如何进行监控以及优化?
      一、是什么Node作为一门服务端语言,性能方面尤为重要,其衡量指标一般有如下:CPU内存I/O网络CPU主要分成了两部分:CPU负载:在某个时间段内,占用以及等待CPU的进程总数CPU使用率:CPU时间占用状况,等于1-空闲CPU时间(idletime)/CPU总时间这两个指标都是用来评估......
  • MVVM模式的优点以及与MVC模式的区别?
    1.MVVM模式的优点:1、低耦合:视图(View)可以独⽴于Model变化和修改,⼀个ViewModel可以绑定到不同的"View"上,当View变化的时候Model可以不变,当Model变化的时候View也可以不变。2、可重⽤性:你可以把⼀些视图逻辑放在⼀个ViewModel⾥⾯,让很多......
  • linux环境开发过程中遇到的问题以及解决办法 how to
    1.装好开发机后上网检查网口是否开通,网线是否正常一般桌面上多个网口可能只开通一个,需要联系IT联系之前可以先找一个正常的网线连到PC看看是否能够提供网络一般从公司内网连接外网需要连接代理检查proxy的可访问性curl-x<proxy_address>:<proxy_port>http://www.example......