Files
code-utils/network_info.go
李琦 e71ce451ff 模块介绍 / 今日规划 / 下班日报 / 团队摘要 生成成功后没有上报,也几乎不写「AI」日志。
流结束时的 usage 用了非阻塞发送,通道一满就被丢掉;很多服务商默认还不在流里带 usage。
现在每次真正打到模型的调用都会:

在本地日志(分类 AI,消息 AI 调用完成)写下 scene / prompt / completion / cached
登录后同步上报到后台日汇总
2026-08-17 14:00:47 +08:00

277 lines
7.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package main
// network_info.go采集本机网卡信息Windows 跑 ipconfig /all 并结构化解析,其它平台用 Go net
// 自动识别主局域网 IPv4供启动台一键复制。
import (
"net"
"os"
"os/exec"
"runtime"
"strings"
)
// NetAdapter 单块网卡的摘要信息。
type NetAdapter struct {
Name string `json:"name"`
Description string `json:"description,omitempty"`
MAC string `json:"mac,omitempty"`
IPv4 []string `json:"ipv4,omitempty"`
IPv6 []string `json:"ipv6,omitempty"`
Gateway []string `json:"gateway,omitempty"`
DNS []string `json:"dns,omitempty"`
DHCP bool `json:"dhcp"`
Primary bool `json:"primary"`
Virtual bool `json:"virtual"`
}
// NetworkInfo 本机网络总览。
type NetworkInfo struct {
Hostname string `json:"hostname"`
PrimaryIPv4 string `json:"primaryIPv4"`
Adapters []NetAdapter `json:"adapters"`
Raw string `json:"raw"`
}
// GetNetworkInfo 一键采集本机网卡信息。
func (a *App) GetNetworkInfo() (NetworkInfo, error) {
out := NetworkInfo{Adapters: []NetAdapter{}}
out.Hostname, _ = os.Hostname()
if runtime.GOOS == "windows" {
raw, err := exec.Command("powershell", "-NoProfile", "-Command",
"[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; ipconfig /all").CombinedOutput()
if err != nil || len(raw) == 0 {
raw, err = exec.Command("ipconfig", "/all").CombinedOutput()
}
out.Raw = string(raw)
if err == nil && len(raw) > 0 {
out.Adapters = parseIPConfig(out.Raw)
}
}
if len(out.Adapters) == 0 {
out.Adapters = listAdaptersGo()
if out.Raw == "" {
out.Raw = formatAdaptersRaw(out.Hostname, out.Adapters)
}
}
markPrimary(&out)
return out, nil
}
func isVirtualName(s string) bool {
low := strings.ToLower(s)
for _, k := range []string{
"virtual", "vmware", "vbox", "virtualbox", "hyper-v", "vethernet",
"wsl", "docker", "vpn", "tap-windows", "zerotier", "tailscale",
"loopback", "蓝牙", "bluetooth", "isatap", "teredo", "microsoft wi-fi direct",
} {
if strings.Contains(low, k) {
return true
}
}
return false
}
func isPrivateIPv4(ip string) bool {
p := net.ParseIP(strings.Split(ip, "/")[0])
if p == nil {
return false
}
v4 := p.To4()
if v4 == nil {
return false
}
return v4[0] == 10 ||
(v4[0] == 172 && v4[1] >= 16 && v4[1] <= 31) ||
(v4[0] == 192 && v4[1] == 168)
}
func markPrimary(info *NetworkInfo) {
best := -1
bestScore := -1
for i := range info.Adapters {
info.Adapters[i].Virtual = isVirtualName(info.Adapters[i].Name + " " + info.Adapters[i].Description)
score := 0
if info.Adapters[i].Virtual {
score -= 50
}
for _, ip := range info.Adapters[i].IPv4 {
if strings.HasPrefix(ip, "127.") {
continue
}
score += 10
if isPrivateIPv4(ip) {
score += 30
}
if len(info.Adapters[i].Gateway) > 0 {
score += 20
}
break
}
if score > bestScore {
bestScore = score
best = i
}
}
if best >= 0 && bestScore > 0 {
info.Adapters[best].Primary = true
for _, ip := range info.Adapters[best].IPv4 {
if !strings.HasPrefix(ip, "127.") {
info.PrimaryIPv4 = strings.Split(ip, "/")[0]
break
}
}
}
}
// parseIPConfig 解析 Windows `ipconfig /all` 文本(兼容中英文关键字)。
func parseIPConfig(raw string) []NetAdapter {
lines := strings.Split(strings.ReplaceAll(raw, "\r\n", "\n"), "\n")
var list []NetAdapter
var cur *NetAdapter
flush := func() {
if cur == nil {
return
}
if cur.Name != "" || len(cur.IPv4) > 0 || len(cur.IPv6) > 0 {
list = append(list, *cur)
}
cur = nil
}
for _, line := range lines {
trim := strings.TrimSpace(line)
if trim == "" {
continue
}
// 适配器标题行:不以空格开头,且以冒号结束或含“适配器/adapter”
if len(line) > 0 && line[0] != ' ' && line[0] != '\t' {
flush()
name := strings.TrimRight(trim, ":")
cur = &NetAdapter{Name: name}
continue
}
if cur == nil {
continue
}
key, val, ok := splitKV(trim)
if !ok {
continue
}
lk := strings.ToLower(key)
switch {
case strings.Contains(lk, "description") || strings.Contains(key, "描述"):
cur.Description = val
case strings.Contains(lk, "physical address") || strings.Contains(key, "物理地址"):
cur.MAC = val
case strings.Contains(lk, "dhcp enabled") || strings.Contains(key, "dhcp 已启用") || strings.Contains(key, "DHCP 已启用"):
cur.DHCP = strings.EqualFold(val, "yes") || val == "是"
case strings.Contains(lk, "ipv4") || strings.Contains(key, "IPv4"):
cur.IPv4 = append(cur.IPv4, cleanIP(val))
case strings.Contains(lk, "ipv6") || strings.Contains(key, "IPv6"):
if !strings.HasPrefix(strings.ToLower(val), "fe80") {
cur.IPv6 = append(cur.IPv6, cleanIP(val))
}
case strings.Contains(lk, "default gateway") || strings.Contains(key, "默认网关"):
if v := cleanIP(val); v != "" {
cur.Gateway = append(cur.Gateway, v)
}
case strings.Contains(lk, "dns servers") || strings.Contains(key, "dns 服务器") || strings.Contains(key, "DNS 服务器"):
if v := cleanIP(val); v != "" {
cur.DNS = append(cur.DNS, v)
}
}
}
flush()
return list
}
func splitKV(line string) (key, val string, ok bool) {
// ipconfig 用 ". . . . :" 或直接 ":" 分隔
idx := strings.LastIndex(line, ":")
if idx <= 0 {
return "", "", false
}
key = strings.TrimSpace(strings.TrimRight(line[:idx], ". \t"))
val = strings.TrimSpace(line[idx+1:])
if key == "" {
return "", "", false
}
return key, val, true
}
func cleanIP(v string) string {
v = strings.TrimSpace(v)
// 去掉 "(首选)" / "(Preferred)" 等后缀
if i := strings.IndexAny(v, " ("); i > 0 {
v = strings.TrimSpace(v[:i])
}
return v
}
func listAdaptersGo() []NetAdapter {
ifaces, err := net.Interfaces()
if err != nil {
return nil
}
var list []NetAdapter
for _, iface := range ifaces {
if iface.Flags&net.FlagLoopback != 0 {
continue
}
addrs, _ := iface.Addrs()
a := NetAdapter{Name: iface.Name, MAC: iface.HardwareAddr.String()}
for _, addr := range addrs {
s := addr.String()
ip, _, _ := net.ParseCIDR(s)
if ip == nil {
ip = net.ParseIP(s)
}
if ip == nil {
continue
}
if v4 := ip.To4(); v4 != nil {
a.IPv4 = append(a.IPv4, v4.String())
} else if !ip.IsLinkLocalUnicast() {
a.IPv6 = append(a.IPv6, ip.String())
}
}
if len(a.IPv4) == 0 && len(a.IPv6) == 0 {
continue
}
list = append(list, a)
}
return list
}
func formatAdaptersRaw(host string, adapters []NetAdapter) string {
var b strings.Builder
b.WriteString("Hostname: " + host + "\n\n")
for _, a := range adapters {
b.WriteString(a.Name)
if a.Description != "" {
b.WriteString(" (" + a.Description + ")")
}
b.WriteString(":\n")
if a.MAC != "" {
b.WriteString(" MAC: " + a.MAC + "\n")
}
for _, ip := range a.IPv4 {
b.WriteString(" IPv4: " + ip + "\n")
}
for _, ip := range a.IPv6 {
b.WriteString(" IPv6: " + ip + "\n")
}
for _, g := range a.Gateway {
b.WriteString(" Gateway: " + g + "\n")
}
for _, d := range a.DNS {
b.WriteString(" DNS: " + d + "\n")
}
b.WriteString("\n")
}
return b.String()
}