常用业务代码

go语言在.csv文件中加入超链接 #

func main() {
	// 打开文件以写入 CSV 数据
	file, err := os.Create("output.csv")
	if err != nil {
		panic(err)
	}
	defer file.Close()

	// 创建 CSV writer
	writer := csv.NewWriter(file)
	defer writer.Flush()

	// 写入 CSV 头部
	header := []string{"File Name", "Hyperlink"}
	writer.Write(header)

	// 模拟一些文件名和相对路径数据
	fileData := []struct {
		FileName     string
		RelativePath string
	}{
		{"video.MP4", "./d/video.MP4"},
		// 添加更多文件名和相对路径
	}

	// 写入文件名和相对路径数据到 CSV 文件
	for _, data := range fileData {
		// 构建超链接字符串
		hyperlinkFormula := `=HYPERLINK("` + data.RelativePath + `", "` + data.FileName + `")`
		row := []string{data.FileName, hyperlinkFormula}
		writer.Write(row)
	}

	// 刷新 CSV writer 缓冲区,确保所有数据被写入文件
	writer.Flush()
}

通过ffmpeg获取视频文件信息 #

//videopath := api.GetMountPoint() + common.FixPathWithSeparator(v.FullPath, "\\")
	//vv, iserr := video.GetVideoInfo.Stat(videopath)
	//taskMutex.Lock()
	//tasked++
	//taskMutex.Unlock()
	//if !iserr {
	//	data.Duration = int(math.Floor(vv.Length)) //秒
	//	data.FrameRate = int(math.Floor(vv.Fps))
	//	data.FrameHeight = int(vv.Height)
	//	data.FrameWidth = int(vv.Width)
	//	data.Resolution = strconv.Itoa(data.FrameWidth) + " * " + strconv.Itoa(data.FrameHeight)
	//}
	//data.UserId=
	//width, height, duration, _, err := getVideoSize(videopath)
	//if err == nil {
	//	data.Duration, _ = strconv.Atoi(duration) //秒
	//	//data.FrameRate = int(math.Floor(vv.Fps))
	//	data.Resolution = strconv.Itoa(width * height)
	//	data.FrameHeight = height
	//	data.FrameWidth = width
	//}
// import ffmpeg "github.com/u2takey/ffmpeg-go"   发现时间效果差不多 都是底层启动多个exe执行
func getVideoSize(fileName string) (width, height int, duration, rframerate string, err error) {
	//log.Println("Getting video size for", fileName)
	data, err := ffmpeg.Probe(fileName)
	if err != nil {
		fmt.Println(err.Error())
		return 0, 0, "", "", err
	}
	//log.Println("got video info", data)
	type VideoInfo struct {
		Streams []struct {
			CodecType  string `json:"codec_type"`
			Width      int
			Height     int
			Duration   string
			RFrameRate string `json:"r_frame_rate"`
		} `json:"streams"`
	}
	vInfo := &VideoInfo{}
	err = json.Unmarshal([]byte(data), vInfo)
	if err != nil {
		return 0, 0, "", "", err
	}
	for _, s := range vInfo.Streams {
		if s.CodecType == "video" {
			return s.Width, s.Height, s.Duration, s.RFrameRate, err
		}
	}
	return 0, 0, "", "", err
}
var GetVideoInfo = new(VideoInfo)

type VideoInfo struct{}
type Video struct {
	//Path     string
	Length float64 // 时长(s)
	//Bitrate  float64 // 播放速率(kb/s)
	//Size     int64   // 文件大小(byte)
	Width  int64   // 视频分辨率宽度
	Height int64   // 视频分辨率高度
	Fps    float64 // 视频帧率(帧/s)
	//Vbitrate float64 // 视频比特率(kb/s)
	//Abitrate float64 // 音频比特率(kb/s)
	//Ahz      float64 // 音频采集率(Hz)
}

// Stat 通过调用 ffmpeg命令 使用正则获取视频信息,
// 部分视频无法正常获取时长或比特率等,则使用0表示;
// 如果多个属性无法获取,则可能是正则匹配不全,
// 请手动执行 ffmpeg -i file_path 参照输出信息来确认问题
//var cmd = exec.Command("ffmpeg", "-i", "video_path")

func (video VideoInfo) Stat(video_path string) (v *Video, iserr bool) {
	//cmd.Args[2] = video_path
	//dir, err := os.Getwd() //获取当前文件路径
	//if err != nil {
	//	fmt.Println(err.Error())
	//	return
	//}
	ffmpegPath := filepath.Join(api.GetAppInstallDir(), "\\bin\\ffmpeg\\ffmpeg.exe")
	//api.Log.Info("ffmpegPath:", ffmpegPath) //
	cmd := exec.Command(ffmpegPath, "-i", video_path)
	r, _ := cmd.CombinedOutput()
	//if err != nil {
	//	api.Log.Error(err.Error()) //
	//	fmt.Println("FFmpeg command execution failed: %s\n", err)
	//}
	// sample1
	//  Duration: 00:00:00.00, start: 0.000000, bitrate: N/A
	//    Stream #0:0: Video: rv40 (RV40 / 0x30345652), yuv420p, 640x480, 25 fps, 25 tbr, 1k tbn, 1k tbc
	//    Stream #0:1: Audio: cook (cook / 0x6B6F6F63), 44100 Hz, mono, fltp, 64 kb/s

	// sample2
	//  Duration: 00:10:23.13, start: 0.000000, bitrate: 1741 kb/s
	//    Stream #0:0: Video: h264 (High) (H264 / 0x34363248), yuv420p(progressive), 352x288 [SAR 1:1 DAR 11:9], 1604 kb/s, 30 fps, 30 tbr, 30 tbn, 60 tbc
	//    Stream #0:1: Audio: mp3 (U[0][0][0] / 0x0055), 44100 Hz, stereo, s16p, 128 kb/s

	// sample3
	//  Duration: 00:17:57.43, start: 0.000000, bitrate: 383 kb/s
	//    Stream regexp.MustCompile(`.*Duration:\s(.*?),.*bitrate:\s(\S+)`)#0:0: Audio: cook (cook / 0x6B6F6F63), 44100 Hz, stereo, fltp, 64 kb/s
	//    Stream #0:1: Video: rv40 (RV40 / 0x30345652), yuv420p, 640x480, 308 kb/s, 23.98 fps, 23.98 tbr, 1k tbn, 1k tbc

	str_r := string([]byte(r))
	if video.parse_err(str_r) {
		return nil, true
	}

	length, _ := video.parse_duration(str_r)          //length, bitrate
	width, height, _, fps := video.parse_video(str_r) //width, height, v_bitrate, fps
	//a_hz, a_bitrate := video.parse_audio(str_r)

	v = &Video{
		//Path:     video_path,
		Length: length,
		//Bitrate:  bitrate,
		//Size:     get_size(video_path),
		Width:  width,
		Height: height,
		Fps:    fps,
		//Vbitrate: v_bitrate,
		//Abitrate: a_bitrate,
		//Ahz:      a_hz,
	}

	return
}

func (video VideoInfo) get_size(path string) int64 {
	file, _ := os.Stat(path)
	return file.Size()
}

var reg_err = regexp.MustCompile(`\[in#\d+ @ [0-9a-fA-F]+\] Error opening input: (.+)`)

// 解析err行
func (video VideoInfo) parse_err(str string) bool {
	s := reg_err.FindString(str)
	if len(s) != 0 {
		api.Log.Error(errors.New(s))
		return true
	}
	return false
}

var reg_duration = regexp.MustCompile(`.*Duration:\s(.*?),.*bitrate:\s(\S+)`)

// 解析Duration行
func (video VideoInfo) parse_duration(str string) (float64, float64) {
	s := reg_duration.FindStringSubmatch(str)
	if len(s) != 3 {
		return 0, 0
	}

	t := strings.Split(s[1], ":")
	length := atof64(t[0])*3600 + atof64(t[1])*60 + atof64(t[2])
	return length, atof64(s[2])
}

var reg_video = regexp.MustCompile(`Stream.*Video.*\s(\d+)x(\d+)(?:.*?(\S+)\skb/s)?.*?(\S+)\sfps`)

// 解析Video行
func (video VideoInfo) parse_video(str string) (int64, int64, float64, float64) {
	s := reg_video.FindStringSubmatch(str)

	if len(s) != 5 {
		return 0, 0, 0, 0
	}
	return atoi64(s[1]), atoi64(s[2]), atof64(s[3]), atof64(s[4])
}

var reg_audio = regexp.MustCompile(`Stream.*Audio.*?(\d+)\sHz.*\s(\S+)\skb/s`)

// 解析Audio行
func (video VideoInfo) parse_audio(str string) (float64, float64) {
	s := reg_audio.FindStringSubmatch(str)

	if len(s) != 3 {
		return 0, 0
	}
	return atof64(s[1]), atof64(s[2])
}

func atoi64(s string) int64 {
	i, _ := strconv.ParseInt(s, 10, 64)
	return i
}

func atof64(s string) float64 {
	i, _ := strconv.ParseFloat(s, 64)
	return i
}

防止电脑进入睡眠状态 #

func disableComputerSleep() (err error) {
	kernel32, err := syscall.LoadLibrary("kernel32.dll")
	if err != nil {
		return
	}
	defer syscall.FreeLibrary(kernel32)
	_SetThreadExecutionState, err := syscall.GetProcAddress(kernel32, "SetThreadExecutionState")
	if err != nil {
		return
	}

	for {
		_, _, callErr := syscall.Syscall(_SetThreadExecutionState, 1, 0x80000000|0x00000002|0x00000001, 0, 0)
		if callErr != 0 {
			fmt.Println("SetThreadExecutionState error", callErr)
		}
		time.Sleep(30 * 1000 * time.Millisecond)
	}
}

监听进程退出信号 #

func exitSingal() chan os.Signal {
	exitSingal := make(chan os.Signal, 1)
	signal.Notify(exitSingal, syscall.SIGTERM, syscall.SIGKILL, syscall.SIGINT)
	return exitSingal
}
select {
	case sin := <-exitSingal():
		fmt.Printf("get system singal %s ,exit ", sin.String())
}

获取电脑睡眠状态 #

var (
	   powerStatusCode              uint32 = 0
	   eventCh                             = make(chan uint32)
	   ctx, Chancel                        = context.WithCancel(context.Background())
	libPowrProf                            = windows.NewLazySystemDLL("powrprof.dll")
	powerRegisterSuspendResumeNotification=libPowrProf.NewProc("PowerRegisterSuspendResumeNotification")
	powerUnregisterSuspendResumeNotification=libPowrProf.NewProc("PowerUnregisterSuspendResumeNotification")
)

const (
	PBT_APMSUSPEND         uint32 = 4
	PBT_APMRESUMESUSPEND   uint32 = 7
	PBT_APMRESUMEAUTOMATIC uint32 = 18
)
//入口函数
func ListenSystemSleepEvent() {
	NewEventListener(ctx, eventCh)
	for {
		select {
		case powerStatusCode = <-eventCh:
		default:
		}
	}
}

func NewEventListener(haltCtx context.Context, eventCh chan uint32) {
	go func() {
		runtime.LockOSThread()
		defer runtime.UnlockOSThread()

		const (
			_DEVICE_NOTIFY_CALLBACK = 2
		)
		type _DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS struct {
			callback uintptr
			context  uintptr
		}

		var fn interface{} = func(context uintptr, changeType uint32, setting uintptr) uintptr {
			eventCh <- changeType
			return 0
		}

		params := _DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS{
			callback: windows.NewCallback(fn),
		}
		handle := uintptr(0)

		Log.Info("注册电源 暂停/恢复")
		powerRegisterSuspendResumeNotification.Call(
			_DEVICE_NOTIFY_CALLBACK,
			uintptr(unsafe.Pointer(&params)),
			uintptr(unsafe.Pointer(&handle)),
		)

		<-haltCtx.Done()
		Log.Info("取消注册电源 暂停/恢复")
		powerUnregisterSuspendResumeNotification.Call(
			uintptr(unsafe.Pointer(&handle)),
		)
	}()
}

自动填充空格 以格式化输出字符串 #

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
)

func main() {
	// 定义一个JSON数据
	jsonData := []byte(`{"a":1,"b":2}`)

	// 格式化JSON数据
	var formattedData bytes.Buffer
	err := json.Indent(&formattedData, jsonData, "", "  ")
	if err != nil {
		fmt.Println("Error formatting JSON:", err)
		return
	}

	// 输出格式化后的JSON数据
	fmt.Println(formattedData.String())
}

逐行读取文件 #

package main

import (
	"bufio"
	"fmt"
	"log"
	"os"
)

func main() {
	filePath := "your_file.log"

	// 打开文件
	file, err := os.Open(filePath)
	if err != nil {
		log.Fatal(err)
	}
	defer file.Close()

	// 创建一个 Scanner 来逐行读取文件内容
	scanner := bufio.NewScanner(file)

	// 逐行读取并处理文件内容
	for scanner.Scan() {
		line := scanner.Text()
		fmt.Println(line)

		// 在这里可以对每一行的内容进行处理
		// 例如,你可以将每一行的内容存储到切片中,或者进行其他操作
	}

	// 检查是否有错误发生
	if err := scanner.Err(); err != nil {
		log.Fatal(err)
	}
}

判断windows系统版本 #

import "github.com/elastic/go-sysinfo"
func TestGetVer(t *testing.T) {
	host, err := sysinfo.Host()
	if err != nil {
		fmt.Println("Error getting host info:", err)
		return
	}

	info := host.Info()
	fmt.Println("Operating System:", info.OS.Name)
	fmt.Println("Family:", info.OS.Family)
	fmt.Println("Version:", info.OS.Version)
	fmt.Println("Platform:", info.OS.Platform)
	fmt.Println("Kernel Version:", info.KernelVersion)
	fmt.Println("Arch:", info.Architecture)
}
Operating System:Windows 7 Home Basic
Family: windows
Uersion: 6.1
Platform: windows
Kernel version:6.1.2601.17514(win7sp1_rtm.101119-1850)
Arch:x86_64
0perating System: windows
Version: Windows 6.1<Build 761>

逐字节读取文件 #

func Test_copy(t *testing.T) {
	path1 := "C:\\hlnet\\1-1720405740\\小米行车记录仪MJHSJJLYBY-168862538.E01\\NO NAME\\$未分配簇"
	path2, _ := os.Getwd()
	path2 = filepath.Join(path2, "$未分配簇3")
	time1 := time.Now()
	file, err := os.Open(path1)
	if err != nil {
		fmt.Println(err.Error())
		return
	}
	defer file.Close()

	reader := bufio.NewReader(file)

	bufferSize := 10240
	buffer := make([]byte, bufferSize)

	for {
		n, err := reader.Read(buffer)
		if err != nil {
			fmt.Println(err.Error())
			if err == io.EOF {
				fmt.Println("EOF")
			}
			break // 文件读取结束或发生错误
		}
		// 处理读取的数据
		zeroBuffer := make([]byte, 1024)
		if !bytes.Equal(buffer, zeroBuffer[:n]) {

			fmt.Println("zero buffer")
		}

	}
	fmt.Println(time.Now().Sub(time1).Seconds())
}

判断磁盘端口是2.0还是3.0 #

powershell命令

PS C:\WINDOWS\system32> $ErrorActionPreference='SilentlyContinue'
>> $PhysicalDrive='\\.\PHYSICALDRIVE2'
>>
>> function Get-Parent([string]$id){
>>   (Get-PnpDeviceProperty -InstanceId $id -KeyName 'DEVPKEY_Device_Parent').Data
>> }
>> function Find-BiosUp([string]$startId){
>>   $id=$startId
>>   for($i=0;$i -lt 64 -and $id;$i++){
>>     $v=(Get-PnpDeviceProperty -InstanceId $id -KeyName 'DEVPKEY_Device_BiosDeviceName').Data
>>     if($v){ return $v }
>>     $id=Get-Parent $id
>>   }
>>   return ''
>> }
>>
>> # Win32_DiskDrive → USBSTOR
>> $disk = Get-CimInstance Win32_DiskDrive | Where-Object { $_.DeviceID -eq $PhysicalDrive }
>> if(-not $disk){ return }
>> $storId = $disk.PNPDeviceID
>> $cid    = (Get-PnpDeviceProperty -InstanceId $storId -KeyName 'DEVPKEY_Device_ContainerId').Data
>>
>> # ContainerId 映射到 USB\VID_*
>> $usb = Get-PnpDevice -PresentOnly | Where-Object {
>>   $_.InstanceId -like 'USB\VID_*' -and
>>   (Get-PnpDeviceProperty -InstanceId $_.InstanceId -KeyName 'DEVPKEY_Device_ContainerId').Data -eq $cid
>> } | Select-Object -First 1
>> if(-not $usb){ return }
>>
>> # 端口标记 HSxx/SSxx → 端口节点 → BiosDeviceName
>> $locIds = (Get-PnpDeviceProperty -InstanceId $usb.InstanceId -KeyName 'DEVPKEY_Device_LocationIds').Data
>> $portToken = ''
>> if($locIds){
>>   $m=[regex]::Match($locIds,'ACPI\((HS\d+|SS\d+)\)')
>>   if($m.Success){ $portToken=$m.Groups[1].Value }
>> }
>>
>> $bios=''
>> if($portToken){
>>   $portNode = Get-PnpDevice -PresentOnly | Where-Object {
>>     (Get-PnpDeviceProperty -InstanceId $_.InstanceId -KeyName 'DEVPKEY_Device_LocationIds').Data -match [regex]::Escape("($portToken)")
>>   } | Select-Object -First 1
>>   if($portNode){
>>     $bios=(Get-PnpDeviceProperty -InstanceId $portNode.InstanceId -KeyName 'DEVPKEY_Device_BiosDeviceName').Data
>>   }
>> }
>> if(-not $bios){ $bios = Find-BiosUp $usb.InstanceId }
>>
>> if($bios){ Write-Output $bios }
\_SB.PC00.XHCI.RHUB.SS03

USBTreeView #

USB 设备树查看器(简称 UsbTreeView)基于 Windows 驱动程序开发工具包中的 Microsoft“USBView”示例应用程序

https://www.uwe-sieber.de/usbtreeview_e.html#download

通过PNPDeviceID读取DEVPKEY_Device_BiosDeviceName代码 #

_SB.PC00.XHCI.RHUB.SS01

_SB.PC00.XHCI.RHUB.HS08

路径的逐段分解 #

让我们像解剖一棵树一样,从根到叶来分析这两个路径:

  • _SB (System Bus - 系统总线)
    • 含义: 这是ACPI命名空间的,代表了计算机最核心的系统总线。所有设备都可以看作是挂在这棵“树”的根上。
  • .PC00 (PCI Bus 0 - PCI总线0)
    • 含义: 代表主板上的主要PCI或PCI Express总线。几乎所有高性能的板载设备(如显卡、网卡、USB控制器)都连接在这条总线上。00是它的编号。
  • .XHCI (eXtensible Host Controller Interface - 可扩展主机控制器接口)
    • 含义: 这就是您电脑上那个统一的、先进的USB 3.x主控制器。它是一个物理芯片(或芯片组的一部分),负责管理所有的USB通信。
  • .RHUB (Root Hub - 根集线器)
    • 含义: 这是集成在XHCI控制器内部的主集线器。您可以把它想象成一个电源排插的“总插座”,所有的物理USB端口最终都从这里分支出去。

到这里,两个路径都是一样的,说明它们都源自同一个USB 3.0控制器。真正的区别在最后一部分

  • _SB.PC00.XHCI.RHUB.SS01 #

    • SS: 代表 SuperSpeed (超高速)。这是USB 3.0及更高版本的代名词。
    • 01: 代表该集线器上的第1个SuperSpeed端口。

    结论: SS01 代表了连接到XHCI控制器高速通道逻辑端口1。只有物理上是USB 3.0的插孔,才能连接到这个SSxx逻辑端口上。

    _SB.PC00.XHCI.RHUB.HS08 #

    • HS: 代表 High-Speed (高速)。这是USB 2.0的代名词。
    • 08: 代表该集线器上的第8个High-Speed端口。

    结论: HS08 代表了连接到XHCI控制器兼容通道逻辑端口8。无论是物理的USB 2.0插孔,还是物理USB 3.0插孔的2.0兼容部分,都会被映射到一个HSxx逻辑端口上。

type devinst uint32
type DEVPROPTYPE uint32

type DEVPROPKEY struct {
	FmtID windows.GUID
	Pid   uint32
}

var (
	modCfgMgr32               = windows.NewLazySystemDLL("cfgmgr32.dll")
	procCMLocateDevNodeW      = modCfgMgr32.NewProc("CM_Locate_DevNodeW")
	procCMGetParent           = modCfgMgr32.NewProc("CM_Get_Parent")
	procCMGetDevNodePropertyW = modCfgMgr32.NewProc("CM_Get_DevNode_PropertyW")

	CR_SUCCESS      uint32 = 0x00000000
	CR_BUFFER_SMALL uint32 = 0x0000001A
)

// DEVPKEY_Device_BiosDeviceName = {540B947E-8B40-45BC-A8A2-6A0B894CBDA2}, PID 10
var DEVPKEY_Device_BiosDeviceName = DEVPROPKEY{
	FmtID: windows.GUID{Data1: 0x540b947e, Data2: 0x8b40, Data3: 0x45bc, Data4: [8]byte{0xa8, 0xa2, 0x6a, 0x0b, 0x89, 0x4c, 0xbd, 0xa2}},
	Pid:   10,
}

func cmLocateDevNodeW(id string) (devinst, uint32) {
	p, _ := windows.UTF16PtrFromString(id)
	var dn devinst
	r1, _, _ := procCMLocateDevNodeW.Call(uintptr(unsafe.Pointer(&dn)), uintptr(unsafe.Pointer(p)), 0)
	return dn, uint32(r1)
}

func cmGetParent(child devinst) (devinst, uint32) {
	var parent devinst
	r1, _, _ := procCMGetParent.Call(uintptr(unsafe.Pointer(&parent)), uintptr(child), 0)
	return parent, uint32(r1)
}

func cmGetDevNodePropertyString(dn devinst, key *DEVPROPKEY) (string, uint32) {
	var typ DEVPROPTYPE
	var needed uint32

	// 1) 探测所需大小:期望 CR_BUFFER_SMALL
	r1, _, _ := procCMGetDevNodePropertyW.Call(
		uintptr(dn),
		uintptr(unsafe.Pointer(key)),
		uintptr(unsafe.Pointer(&typ)),
		0,
		uintptr(unsafe.Pointer(&needed)),
		0,
	)
	if uint32(r1) != CR_BUFFER_SMALL && uint32(r1) != CR_SUCCESS {
		return "", uint32(r1)
	}
	if needed == 0 {
		// 属性存在但空/或非字符串
		return "", CR_SUCCESS
	}

	// 2) 真实读取
	buf := make([]uint16, needed/2)
	r2, _, _ := procCMGetDevNodePropertyW.Call(
		uintptr(dn),
		uintptr(unsafe.Pointer(key)),
		uintptr(unsafe.Pointer(&typ)),
		uintptr(unsafe.Pointer(&buf[0])),
		uintptr(unsafe.Pointer(&needed)),
		0,
	)
	if uint32(r2) != CR_SUCCESS {
		return "", uint32(r2)
	}

	// UTF-16 零终止
	return windows.UTF16PtrToString(&buf[0]), CR_SUCCESS
}

func queryBiosFromUsbStorPNP(storPNP string) (string, error) {
	dn, cr := cmLocateDevNodeW(storPNP)
	if cr != CR_SUCCESS {
		return "", fmt.Errorf("CM_Locate_DevNodeW failed: 0x%x", cr)
	}
	for i := 0; i < 64; i++ {
		if name, cr2 := cmGetDevNodePropertyString(dn, &DEVPKEY_Device_BiosDeviceName); cr2 == CR_SUCCESS && name != "" {
			return name, nil
		}
		parent, crp := cmGetParent(dn)
		if crp != CR_SUCCESS {
			break
		}
		dn = parent
	}
	return "", nil
}