package main import ( "bytes" "encoding/json" "fmt" "io" "net/http" "net/url" "os" "os/signal" "path/filepath" "reflect" "runtime" "strconv" "strings" "sync" "syscall" "time" "unsafe" "qiweimanager/config" ) // 全局变量 var ( // 全局clientId,用于socket回调处理 globalClientId uint32 clientIdMutex sync.Mutex ) func main() { // 首先隐藏控制台窗口 hideConsoleWindow() // 确保在32位系统上运行 if runtime.GOARCH != "386" && runtime.GOARCH != "amd64" { fmt.Fprintf(os.Stderr, "错误: 辅助程序必须在32位或64位系统上运行,当前系统架构: %s\n", runtime.GOARCH) exePath, _ := os.Executable() exeDir := filepath.Dir(exePath) errFile, _ := os.Create(filepath.Join(exeDir, "architecture_error.txt")) if errFile != nil { errFile.WriteString(fmt.Sprintf("Architecture error: %s\n", runtime.GOARCH)) errFile.Close() } os.Exit(1) } // 初始化日志 initLogger() if globalLogger == nil { fmt.Fprintf(os.Stderr, "[严重错误] 日志系统初始化失败,无法继续运行\n") exePath, _ := os.Executable() exeDir := filepath.Dir(exePath) errFile, _ := os.Create(filepath.Join(exeDir, "logger_init_failed.txt")) if errFile != nil { errFile.WriteString("Logger initialization failed\n") errFile.Close() } os.Exit(1) } // 记录启动日志 globalLogger.Info("[辅助程序] 32位辅助程序已启动") // 初始化全局配置 exeName := "helper" if err := config.InitGlobalConfig(exeName, globalLogger); err != nil { globalLogger.Error("[辅助程序] 初始化全局配置失败: %v", err) } else { globalLogger.Info("[辅助程序] 全局配置初始化成功") } // 尝试加载配置 appConfig := config.GetGlobalConfig() if appConfig != nil { globalLogger.Info("[辅助程序] 成功加载全局配置,回调功能状态: %v", appConfig.CallbackConfig.EnableCallback) } else { globalLogger.Warn("[辅助程序] 未能加载全局配置,将使用默认值") } initAutoReplyEngine() initAfterSalesIssueEngine() startKingdeeMonitorFromConfig() // 获取程序路径 exePath, err := os.Executable() if err == nil { exeDir := filepath.Dir(exePath) globalLogger.Info("[辅助程序] 程序路径: %s", exePath) globalLogger.Info("[辅助程序] 程序目录: %s", exeDir) // 定义DLL文件路径 dllBundle := resolveDLLBundle() helperDLLPath := dllBundle.HelperPath loaderDLLPath := dllBundle.LoaderPath globalLogger.Info("[辅助程序] 企业微信版本: %s, 使用Helper: %s, 使用Loader: %s", dllBundle.WxWorkVersion, helperDLLPath, loaderDLLPath) if dllBundle.Message != "" { globalLogger.Warn("[辅助程序] DLL版本提示: %s", dllBundle.Message) } // 加载Helper DLL helperDLL, helperErr := loadDLL(helperDLLPath) if helperErr == nil { defer func() { if helperDLL != 0 { syscall.FreeLibrary(helperDLL) globalLogger.Info("[辅助程序] 已释放Helper DLL") } }() } else { globalLogger.Debug("[错误] 加载Helper DLL失败: %v", helperErr) } // 加载Loader DLL loaderDLL, loaderErr := loadDLL(loaderDLLPath) if loaderErr == nil { // 获取Loader DLL中的所有函数 loaderFuncs, funcsErr := getLoaderFunctions(loaderDLL) // 保存到全局变量 loaderFuncsMutex.Lock() globalLoaderFuncs = loaderFuncs loaderFuncsMutex.Unlock() if funcsErr == nil { // 尝试获取企业微信版本 - 增加额外的安全检查,暂时跳过此调用以避免崩溃 globalLogger.Info("[辅助程序] 跳过获取企业微信版本,因为该操作可能导致崩溃") /* version, versionErr := getUserWxWorkVersion(loaderFuncs.GetUserWxWorkVersion) if versionErr == nil { globalLogger.Info("[辅助程序] 当前企业微信版本: %s", version) } else { globalLogger.Warn("[警告] 获取企业微信版本失败: %v", versionErr) } */ // 尝试使用UseUtf8函数 globalLogger.Info("[辅助程序] 尝试设置UTF-8编码") ret, _, callErr := syscall.Syscall( loaderFuncs.UseUtf8, 0, 0, 0, 0, ) if ret != 0 && callErr == 0 { globalLogger.Info("[辅助程序] 成功设置UTF-8编码") } else { globalLogger.Warn("[警告] 设置UTF-8编码失败: 返回值=%d, 错误=%v", ret, callErr) } // 尝试使用UseRecvJsUnicode函数 globalLogger.Info("[辅助程序] 尝试设置接收JS Unicode编码") ret, _, callErr = syscall.Syscall( loaderFuncs.UseRecvJsUnicode, 0, 0, 0, 0, ) if ret != 0 && callErr == 0 { globalLogger.Info("[辅助程序] 成功设置接收JS Unicode编码") } else { globalLogger.Warn("[警告] 设置接收JS Unicode编码失败: 返回值=%d, 错误=%v", ret, callErr) } // 尝试设置数据位置路径 /*dataPath := filepath.Join(exeDir, "WXWorkData") globalLogger.Info("[辅助程序] 尝试设置数据位置路径: %s", dataPath) cDataPath := syscall.StringToUTF16Ptr(dataPath) ret, _, callErr = syscall.Syscall6( loaderFuncs.SetDataLocationPath, 1, uintptr(unsafe.Pointer(cDataPath)), 0, 0, 0, 0, 0, ) if ret != 0 && callErr == 0 { globalLogger.Info("[辅助程序] 成功设置数据位置路径") } else { globalLogger.Warn("[警告] 设置数据位置路径失败: 返回值=%d, 错误=%v", ret, callErr) }*/ // 初始化企业微信Socket连接并设置回调函数 globalLogger.Info("[辅助程序] 尝试初始化企业微信Socket连接") success, initErr := InitWxWorkSocket() if initErr != nil { globalLogger.Warn("[警告] 初始化企业微信Socket连接失败: %v", initErr) } else if success { globalLogger.Info("[辅助程序] 成功初始化企业微信Socket连接") } // 创建完成通道 /*doneChan := make(chan struct{}) // 启动一个goroutine来定期搜索WXWork.exe进程并注入DLL go func() { ticker := time.NewTicker(5 * time.Second) defer ticker.Stop() injectedPIDs := make(map[uint32]bool) // 记录已注入的进程ID for { select { case <-doneChan: return case <-ticker.C: globalLogger.Info("[辅助程序] 搜索WXWork.exe进程") processIDs, processErr := findProcessByName("WXWork.exe") if processErr == nil { // 遍历所有找到的进程ID并注入DLL for _, pid := range processIDs { // 只向未注入过的进程注入DLL if !injectedPIDs[pid] { globalLogger.Info("[辅助程序] 找到未注入的WXWork进程,进程ID: %d", pid) if loaderFuncs != nil && loaderFuncs.InjectWxWorkPid != 0 { // 调用InjectWxWorkPid函数向进程注入DLL globalLogger.Info("[辅助程序] 尝试向进程ID %d 注入DLL", pid) injected, injectErr := injectIntoProcess(loaderFuncs.InjectWxWorkPid, pid, helperDLLPath) if injectErr != nil { globalLogger.Error("[错误] 向进程ID %d 注入DLL失败: %v", pid, injectErr) } else if injected { globalLogger.Info("[辅助程序] 成功向进程ID %d 注入DLL", pid) injectedPIDs[pid] = true // 注入成功后,停止搜索新的进程 globalLogger.Info("[辅助程序] 已成功注入至少一个进程,停止搜索WXWork.exe进程") close(doneChan) return } } else { globalLogger.Error("[错误] 无法向进程ID %d 注入DLL,因为InjectWxWorkPid函数不可用", pid) } } } } } } }()*/ // 在程序退出前调用DestroyWxWork函数释放资源 defer func() { globalLogger.Info("[辅助程序] 正在调用DestroyWxWork释放资源...") ret, _, callErr := syscall.Syscall( loaderFuncs.DestroyWxWork, 0, 0, 0, 0, ) if ret != 0 && callErr == 0 { globalLogger.Info("[辅助程序] 成功调用DestroyWxWork释放资源") } else { globalLogger.Warn("[警告] 调用DestroyWxWork失败: 返回值=%d, 错误=%v", ret, callErr) } }() } else { globalLogger.Error("[错误] 获取Loader函数失败: %v", funcsErr) } defer func() { if loaderDLL != 0 { syscall.FreeLibrary(loaderDLL) globalLogger.Info("[辅助程序] 已释放Loader DLL") } }() } else { globalLogger.Error("[错误] 加载Loader DLL失败: %v", loaderErr) } } else { globalLogger.Error("[错误] 获取程序路径失败: %v", err) } // 启动HTTP REST服务器 go func() { maxRetries := 3 retryInterval := 2 * time.Second var lastErr error for i := 0; i < maxRetries; i++ { globalLogger.Info("[辅助程序] 尝试启动HTTP REST服务器 (尝试 %d/%d)", i+1, maxRetries) err := startHTTPServer() if err == nil { globalLogger.Info("[辅助程序] HTTP REST服务器成功启动") // 启动成功后,定期检查服务器健康状态 /*go func() { ticker := time.NewTicker(30 * time.Second) defer ticker.Stop() for { select { case <-ticker.C: // 执行简单的本地健康检查 resp, err := http.Get("http://localhost:8888/api/health") if err != nil { globalLogger.Warn("[警告] HTTP服务器健康检查失败: %v", err) } else { resp.Body.Close() globalLogger.Debug("[辅助程序] HTTP服务器健康检查通过") } } } }()*/ return } lastErr = err globalLogger.Error("[错误] HTTP REST服务器启动失败 (尝试 %d/%d): %v", i+1, maxRetries, err) // 如果不是最后一次尝试,等待一段时间后重试 if i < maxRetries-1 { globalLogger.Info("[辅助程序] %d秒后重试...", retryInterval/time.Second) time.Sleep(retryInterval) } } // 所有重试都失败 globalLogger.Error("[严重错误] HTTP REST服务器启动失败,已尝试所有重试: %v", lastErr) // 记录错误到文件以便排查 /*exePath, _ := os.Executable() exeDir := filepath.Dir(exePath) errFile, _ := os.Create(filepath.Join(exeDir, "http_server_failed.txt")) if errFile != nil { errFile.WriteString(fmt.Sprintf("HTTP server failed: %v\n", lastErr)) errFile.Close() }*/ }() // 启动globalClientMap变化监测goroutine go monitorClientMapChanges() // 设置信号处理,等待中断信号 sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) globalLogger.Info("[辅助程序] 等待退出信号...") <-sigChan globalLogger.Info("[辅助程序] 收到退出信号,开始清理资源...") // 清理资源 cleanup() globalLogger.Info("[辅助程序] 32位辅助程序已退出") } // monitorClientMapChanges 监测globalClientMap变化并写入JSON文件 func monitorClientMapChanges() { // 获取exe路径 exePath, err := os.Executable() if err != nil { globalLogger.Error("[监测器] 获取程序路径失败: %v", err) return } exeDir := filepath.Dir(exePath) configDir := filepath.Join(exeDir, "config") // 创建config目录(如果不存在) if err := os.MkdirAll(configDir, 0755); err != nil { globalLogger.Error("[监测器] 创建config目录失败: %v", err) return } jsonFile := filepath.Join(configDir, "client_status.json") globalLogger.Info("[监测器] 启动globalClientMap变化监测,状态文件: %s", jsonFile) // 记录上一次的状态用于比较 lastStatus := make(map[string]int) // 首次启动只确保文件存在,不清空已有账号信息。 if _, err := os.Stat(jsonFile); os.IsNotExist(err) { if err := os.WriteFile(jsonFile, []byte("{}"), 0644); err != nil { globalLogger.Error("[监测器] 初始化JSON文件失败: %v", err) return } } // 监测循环 for { // 创建当前状态映射 currentStatus := make(map[string]int) // 加锁读取globalClientMap clientIdMutex.Lock() for _, userID := range globalClientMap { if userID != "" { currentStatus[userID] = 1 } } clientIdMutex.Unlock() // 检查状态是否发生变化 if !mapsEqual(lastStatus, currentStatus) { // 找出新增的用户 newUsers := findNewUsers(lastStatus, currentStatus) // 找出消失的用户 removedUsers := findRemovedUsers(lastStatus, currentStatus) // 更新lastStatus记录当前状态 lastStatus = make(map[string]int) for k, v := range currentStatus { lastStatus[k] = v } // 新增用户信息由11026/11179识别流程直接写入client_status.json。 _ = newUsers // 如果有消失用户,更新其status为0 if len(removedUsers) > 0 { go updateRemovedUsersStatus(removedUsers) } } // 每5秒检查一次变化 time.Sleep(5 * time.Second) } } // mapsEqual 比较两个map是否相等 func mapsEqual(a, b map[string]int) bool { if len(a) != len(b) { return false } for k, v := range a { if b[k] != v { return false } } return true } // getLocalHTTPPort 获取本地HTTP服务器端口,避免与http_server.go冲突 func getLocalHTTPPort() int { // 获取全局配置 appConfig := config.GetGlobalConfig() if appConfig != nil && appConfig.CallbackConfig.HTTPPort != "" { // 尝试将字符串端口转换为整数 port, err := strconv.Atoi(appConfig.CallbackConfig.HTTPPort) if err == nil && port > 0 && port <= 65535 { return port } } // 使用默认值 return 10001 } // findNewUsers 找出新增的用户 func findNewUsers(oldStatus, newStatus map[string]int) []string { var newUsers []string for userID := range newStatus { if _, exists := oldStatus[userID]; !exists { newUsers = append(newUsers, userID) } } return newUsers } // findRemovedUsers 找出消失的用户 func findRemovedUsers(oldStatus, newStatus map[string]int) []string { var removedUsers []string for userID := range oldStatus { if _, exists := newStatus[userID]; !exists { removedUsers = append(removedUsers, userID) } } return removedUsers } // sendNewUsersToThirdParty 向第三方接口发送新增用户数据 func sendNewUsersToThirdParty(newUsers []string) { // 构建请求数据 params := make(map[string]interface{}) params["robotId"] = newUsers[0] params["instanceId"] = "" requestData := map[string]interface{}{ "type": "getCurrentAccountInfo", "params": params, } // 转换为JSON jsonData, err := json.Marshal(requestData) if err != nil { globalLogger.Error("[监测器] 构建第三方请求数据失败: %v", err) return } // 获取exe路径,构建完整URL exePath, err := os.Executable() if err != nil { globalLogger.Error("[监测器] 获取程序路径失败: %v", err) return } exeDir := filepath.Dir(exePath) // 构建完整的第三方接口URL // 使用与HTTP服务器相同的端口 port := getLocalHTTPPort() thirdPartyURL := fmt.Sprintf("http://localhost:%d/api/third-party-request", port) // 发送POST请求 resp, err := http.Post(thirdPartyURL, "application/json", bytes.NewBuffer(jsonData)) if err != nil { globalLogger.Error("[监测器] 向第三方接口发送请求失败: %v", err) return } defer resp.Body.Close() // 读取响应 responseBody, err := io.ReadAll(resp.Body) if err != nil { globalLogger.Error("[监测器] 读取第三方接口响应失败: %v", err) return } // 解析响应数据中的用户信息 var apiResponse map[string]interface{} if err := json.Unmarshal(responseBody, &apiResponse); err != nil { globalLogger.Error("[监测器] 解析第三方接口响应失败: %v", err) return } // 提取用户信息数据 var userData map[string]interface{} if data, ok := apiResponse["data"].(map[string]interface{}); ok { if userInfo, ok := data["data"].(map[string]interface{}); ok { userData = userInfo } } // 如果没有提取到用户信息,记录错误并返回 if userData == nil { globalLogger.Error("[监测器] 无法从响应中提取用户信息") return } // 提取user_id作为key userID, ok := userData["user_id"].(string) if !ok || userID == "" { globalLogger.Error("[监测器] 响应中缺少user_id字段") return } // 添加status字段,默认值为1 userData["status"] = 1 // 构建client_status.json文件路径 clientStatusFile := filepath.Join(exeDir, "config", "client_status.json") // 读取现有的client_status.json文件 var clientStatus map[string]interface{} if data, err := os.ReadFile(clientStatusFile); err == nil { if err := json.Unmarshal(data, &clientStatus); err != nil { globalLogger.Error("[监测器] 读取client_status文件失败: %v", err) clientStatus = make(map[string]interface{}) } } else { // 如果文件不存在,创建一个新的map clientStatus = make(map[string]interface{}) } // 增量更新:添加或更新用户信息 clientStatus[userID] = userData // 转换为格式化的JSON clientStatusJSON, err := json.MarshalIndent(clientStatus, "", " ") if err != nil { globalLogger.Error("[监测器] 序列化client_status数据失败: %v", err) return } // 写入client_status.json文件(增量更新,不覆盖其他用户) if err := os.WriteFile(clientStatusFile, clientStatusJSON, 0644); err != nil { globalLogger.Error("[监测器] 写入client_status文件失败: %v", err) return } globalLogger.Info("[监测器] 已向第三方接口发送新增用户数据: %v, 用户信息已保存到: %s", newUsers[0], clientStatusFile) } // updateRemovedUsersStatus 更新消失用户的status为0 func updateRemovedUsersStatus(removedUsers []string) { // 获取exe路径 exePath, err := os.Executable() if err != nil { globalLogger.Error("[监测器] 获取程序路径失败: %v", err) return } exeDir := filepath.Dir(exePath) clientStatusFile := filepath.Join(exeDir, "config", "client_status.json") if err := os.MkdirAll(filepath.Dir(clientStatusFile), 0755); err != nil { globalLogger.Error("[辅助程序] 创建client_status目录失败: %v", err) return } // 读取现有的client_status.json文件 var clientStatus map[string]interface{} if data, err := os.ReadFile(clientStatusFile); err == nil { if err := json.Unmarshal(data, &clientStatus); err != nil { globalLogger.Error("[监测器] 读取client_status文件失败: %v", err) return } } else { // 如果文件不存在,创建一个新的map clientStatus = make(map[string]interface{}) } // 更新消失用户的status为0 updated := false for _, userID := range removedUsers { if userData, exists := clientStatus[userID]; exists { if userMap, ok := userData.(map[string]interface{}); ok { userMap["status"] = 0 updated = true globalLogger.Info("[监测器] 用户 %s 已消失,status已更新为0", userID) } } } // 如果有更新,写入文件 if updated { clientStatusJSON, err := json.MarshalIndent(clientStatus, "", " ") if err != nil { globalLogger.Error("[监测器] 序列化client_status数据失败: %v", err) return } if err := os.WriteFile(clientStatusFile, clientStatusJSON, 0644); err != nil { globalLogger.Error("[监测器] 写入client_status文件失败: %v", err) return } globalLogger.Info("[监测器] 已更新消失用户的status,文件保存到: %s", clientStatusFile) } } // cleanup 清理资源 func cleanup() { globalLogger.Info("[辅助程序] 正在清理资源...") // 关闭HTTP服务器 shutdownHTTPServer() } // handleSendWxWorkData 处理来自主程序的SendWxWorkData请求 func handleSendWxWorkData(params map[string]interface{}) (interface{}, error) { // 从参数中获取数据 dataValue, exists := params["data"] if !exists { globalLogger.Error("[错误] 缺少data参数") return map[string]interface{}{"success": false, "error": "缺少data参数"}, nil } // 尝试将数据转换为字符串 // 处理dataValue为字符串或JSON对象的情况 var jsonData string // 检查是否为字符串类型 if strValue, ok := dataValue.(string); ok { jsonData = strValue } else if objValue, ok := dataValue.(map[string]interface{}); ok { // 如果是JSON对象,将其转换为JSON字符串 jsonBytes, err := json.Marshal(objValue) if err != nil { globalLogger.Error("[错误] 转换JSON对象失败: %v", err) return map[string]interface{}{"success": false, "error": "转换JSON对象失败"}, nil } jsonData = string(jsonBytes) } else { globalLogger.Error("[错误] data参数类型错误: %T", dataValue) return map[string]interface{}{"success": false, "error": "data参数类型错误,支持字符串和JSON对象"}, nil } globalLogger.Info("[辅助程序] 收到SendWxWorkData原请求,数据: %s", jsonData) // 处理网络资源(下载图片、视频等到本地) var rData map[string]interface{} var processedJsonData string if err := json.Unmarshal([]byte(jsonData), &rData); err == nil { if requestType, ok := rData["type"]; ok { if typeInt, ok := requestType.(float64); ok { if typeInt != 11171 { processedJsonData, err = processNetworkResources(jsonData) if err != nil { globalLogger.Warn("[辅助程序] 处理网络资源失败: %v,将使用原始数据", err) processedJsonData = jsonData } else { globalLogger.Info("[辅助程序] 网络资源处理完成") } // 记录请求日志 globalLogger.Info("[辅助程序] 收到SendWxWorkData网络转本地请求,数据: %s", processedJsonData) } else { // 处理type=11171的数据 processedJsonData, err = processType11171Data(jsonData) if err != nil { globalLogger.Warn("[辅助程序] 处理type=11171数据失败: %v,将使用原始数据", err) processedJsonData = jsonData } else { globalLogger.Info("[辅助程序] type=11171数据处理完成") } // 记录请求日志 globalLogger.Info("[辅助程序] 收到SendWxWorkData type=11171请求,处理后数据: %s", processedJsonData) } } } } // 尝试通过Loader DLL发送数据到企业微信 loaderFuncsMutex.Lock() loaderFuncs := globalLoaderFuncs loaderFuncsMutex.Unlock() // 获取clientId - 优先使用传入的clientId var clientId uint32 // 检查params中是否有clientId参数 if clientIdValue, ok := params["clientId"]; ok { globalLogger.Debug("[调试] 传入的clientId原始值: %v (类型: %T)", clientIdValue, clientIdValue) switch v := clientIdValue.(type) { case uint32: if v <= 0 { clientId = GetGlobalClientId() globalLogger.Warn("[辅助程序] 传入的clientId值无效: %d,使用全局clientId: %d", v, clientId) } else { clientId = v globalLogger.Info("[辅助程序] 使用传入的uint32 clientId: %d", clientId) } case int, int32, int64: intVal := reflect.ValueOf(v).Int() if intVal <= 0 || intVal < 0 || intVal > 2147483647 { clientId = GetGlobalClientId() globalLogger.Warn("[辅助程序] 传入的clientId值无效: %d,使用全局clientId: %d", intVal, clientId) } else { clientId = uint32(intVal) globalLogger.Info("[辅助程序] 使用传入的int clientId: %d", clientId) } case float32, float64: floatVal := reflect.ValueOf(v).Float() if floatVal <= 0 || floatVal < 0 || floatVal > 2147483647 { clientId = GetGlobalClientId() globalLogger.Warn("[辅助程序] 传入的clientId值无效: %.0f,使用全局clientId: %d", floatVal, clientId) } else { clientId = uint32(floatVal) globalLogger.Info("[辅助程序] 使用传入的float clientId: %d", clientId) } case string: if clientIdInt, err := strconv.Atoi(strings.TrimSpace(v)); err == nil { if clientIdInt <= 0 || clientIdInt > 2147483647 { clientId = GetGlobalClientId() globalLogger.Warn("[辅助程序] 传入的clientId值无效: %d,使用全局clientId: %d", clientIdInt, clientId) } else { clientId = uint32(clientIdInt) globalLogger.Info("[辅助程序] 使用传入的string clientId: %d", clientId) } } else { clientId = GetGlobalClientId() globalLogger.Warn("[辅助程序] 传入的clientId格式无效,使用全局clientId: %d", clientId) } default: clientId = GetGlobalClientId() globalLogger.Warn("[辅助程序] 传入的clientId类型无效(类型: %T),使用全局clientId: %d", clientIdValue, clientId) } } else { // 没有传入clientId,使用全局clientId clientId = GetGlobalClientId() globalLogger.Info("[辅助程序] 使用全局clientId: %d", clientId) } // 检查是否是启动企微请求(type=11153)或需要回调的请求 // 解析processedJsonData获取type字段 var requestData map[string]interface{} //needCallback := true if err := json.Unmarshal([]byte(processedJsonData), &requestData); err == nil { if requestType, ok := requestData["type"]; ok { if typeInt, ok := requestType.(float64); ok { if typeInt == 10000 { // 这是点击启动企微按钮的请求,需要执行InjectWxWork而不是SendWxWorkData globalLogger.Info("[辅助程序] 检测到启动企微请求(type=10000)") if shouldStart, statusPayload := tryBeginWxWorkInjection(); !shouldStart { globalLogger.Info("[辅助程序] 跳过重复启动企微: %v", statusPayload["message"]) return statusPayload, nil } // 获取程序路径 exePath, err := os.Executable() if err != nil { globalLogger.Error("[错误] 获取程序路径失败: %v", err) setInjectionStatus(injectionStatusFailed, 0, "获取程序路径失败") return map[string]interface{}{"success": false, "error": "获取程序路径失败"}, nil } exeDir := filepath.Dir(exePath) _ = exeDir dllBundle := resolveDLLBundle() helperDLLPath := dllBundle.HelperPath if helperDLLPath == "" || dllBundle.LoaderPath == "" { setInjectionStatus(injectionStatusFailed, 0, dllBundle.Message) return map[string]interface{}{"success": false, "error": dllBundle.Message}, nil } if !dllBundle.Compatible { setInjectionStatus(injectionStatusFailed, 0, dllBundle.Message) return map[string]interface{}{"success": false, "error": dllBundle.Message}, nil } if dllBundle.Message != "" { globalLogger.Warn("[辅助程序] DLL版本提示: %s", dllBundle.Message) } // 执行InjectWxWork函数 globalLogger.Info("[辅助程序] 执行InjectWxWork函数,DLL路径: %s", helperDLLPath) ret := InjectWxWork(helperDLLPath, "") if ret != 0 { globalLogger.Info("[辅助程序] InjectWxWork执行成功,返回进程ID: %d", ret) setInjectionStatus(injectionStatusConnected, ret, "") markPIDInjected(ret) go watchWxWorkConnectionTimeout(ret) go injectAllWxWorkProcesses(helperDLLPath, ret) payload := wxWorkStartStatusPayload("启动企微请求已发送,正在识别账号", true, false) payload["processId"] = ret return payload, nil } else { globalLogger.Error("[错误] 调用InjectWxWork失败") setInjectionStatus(injectionStatusFailed, 0, "调用InjectWxWork失败") return map[string]interface{}{"success": false, "error": "调用InjectWxWork失败"}, nil } /*} else if typeInt == 10001 { // 这是需要回调的请求类型 needCallback = true globalLogger.Info("[辅助程序] 检测到配置回调的参数请求(type=10001)") return map[string]interface{}{"success": true}, nil*/ } else if typeInt == 10002 { // 这是获取活跃客户端数量的请求 globalLogger.Info("[辅助程序] 检测到获取活跃客户端数量请求(type=10002)") // 获取当前活跃的客户端数量 activeCount := recognizedClientCount() globalLogger.Info("[辅助程序] 当前活跃客户端数量: %d", activeCount) // 构建响应数据 response := map[string]interface{}{ "success": true, "type": 10002, "data": map[string]interface{}{ "count": activeCount, "recognizedClientCount": activeCount, "unidentifiedClientCount": unidentifiedClientCount(), "connectionCount": connectedClientCount(), }, } return response, nil } else { globalLogger.Info("[辅助程序] 检测其他请求(type=%.0f)", typeInt) } } } } // 如果全局clientId为0,且不是需要回调的请求,尝试从进程中获取 /*if clientId == 0 && !needCallback { // 查找WXWork.exe进程并获取其进程ID processIDs, err := findProcessByName("WXWork.exe") if err == nil && len(processIDs) > 0 { clientId = processIDs[0] globalLogger.Info("[辅助程序] 找到进程: WXWork.exe, 进程ID: %d,已设置为clientId", clientId) } else { globalLogger.Warn("[辅助程序] 未找到WXWork.exe进程,使用默认clientId: %d", clientId) } } else if clientId == 0 && needCallback { // 对于需要回调的请求,如果clientId为0,使用默认值1 clientId = 1 globalLogger.Info("[辅助程序] 对于需要回调的请求,使用默认clientId: %d", clientId) }*/ // 检查Loader DLL函数是否可用 if loaderFuncs != nil && loaderFuncs.SendWxWorkData != 0 { // 转换JSON数据为ANSI字符串 cJsonData, err := syscall.BytePtrFromString(processedJsonData) if err != nil { globalLogger.Error("[错误] 转换JSON数据失败: %v", err) return map[string]interface{}{"success": false, "error": "转换JSON数据失败"}, nil } // 调用SendWxWorkData函数 // 记录详细的参数信息到日志 globalLogger.Info("[辅助程序] 调用SendWxWorkData函数参数详情:") globalLogger.Info("[辅助程序] - 函数地址: 0x%x", loaderFuncs.SendWxWorkData) globalLogger.Info("[辅助程序] - 参数数量: %d", 2) globalLogger.Info("[辅助程序] - 客户端ID(clientId): %d", clientId) globalLogger.Info("[辅助程序] - JSON数据指针(cJsonData): 0x%x", uintptr(unsafe.Pointer(cJsonData))) globalLogger.Info("[辅助程序] - 保留参数: %d", 0) ret, _, callErr := syscall.Syscall( loaderFuncs.SendWxWorkData, 2, uintptr(clientId), uintptr(unsafe.Pointer(cJsonData)), 0, ) if ret != 0 && callErr == 0 { globalLogger.Info("[辅助程序] SendWxWorkData调用成功") return map[string]interface{}{"success": true}, nil } else { globalLogger.Error("[错误] 调用SendWxWorkData失败: 返回值=%d, 错误=%v", ret, callErr) return map[string]interface{}{"success": false, "error": "调用SendWxWorkData失败"}, nil } } else { globalLogger.Error("[错误] SendWxWorkData函数不可用") return map[string]interface{}{"success": false, "error": "SendWxWorkData函数不可用"}, nil } } // 注意:在handleSendWxWorkData函数中,对于type=11153的请求(点击启动企微按钮), // 执行InjectWxWork函数而不是SendWxWorkData函数,并且在InjectWxWork执行成功后执行InitWxWorkSocket函数 // hideConsoleWindow 隐藏控制台窗口(仅Windows平台有效) func hideConsoleWindow() { if runtime.GOOS == "windows" { // 定义Windows API常量和函数 const ( SW_HIDE = 0x00 ) // 获取kernel32.dll kernel32 := syscall.NewLazyDLL("kernel32.dll") // 获取GetConsoleWindow函数 getConsoleWindow := kernel32.NewProc("GetConsoleWindow") // 获取ShowWindow函数 showWindow := kernel32.NewProc("ShowWindow") // 调用GetConsoleWindow获取控制台窗口句柄 hwnd, _, _ := getConsoleWindow.Call() if hwnd != 0 { // 调用ShowWindow隐藏窗口 showWindow.Call(hwnd, uintptr(SW_HIDE)) } } } // SetGlobalClientId 设置全局clientId func SetGlobalClientId(clientId uint32) { clientIdMutex.Lock() globalClientId = clientId clientIdMutex.Unlock() globalLogger.Info("[辅助程序] 已设置全局clientId: %d", clientId) } // GetGlobalClientId 获取全局clientId func GetGlobalClientId() uint32 { clientIdMutex.Lock() defer clientIdMutex.Unlock() return globalClientId } // MyConnectCallback 连接回调函数,在有新客户端加入时调用 // 函数原型: void __stdcall MyConnectCallback(int iClientId) // 注意:为了兼容syscall.NewCallback,需要返回一个uintptr类型的值 // //export MyConnectCallback func MyConnectCallback(iClientId int32) uintptr { globalLogger.Info("[辅助程序] 收到新客户端连接,clientId: %d", iClientId) // 将clientId设置为全局clientId SetGlobalClientId(uint32(iClientId)) registerClientConnected(uint32(iClientId)) return 0 } // MyRecvCallback 接收回调函数,在接收到新消息时调用 // 函数原型: void __stdcall MyRecvCallback(int iClientId, char* szJsonData, int iLen) // 注意:为了兼容syscall.NewCallback,需要返回一个uintptr类型的值 // //export MyRecvCallback func MyRecvCallback(iClientId int32, szJsonData *int8, iLen int32) uintptr { // 转换数据指针为Go字符串 jsonData := make([]byte, iLen) for i := 0; i < int(iLen); i++ { jsonData[i] = byte(*(*int8)(unsafe.Pointer(uintptr(unsafe.Pointer(szJsonData)) + uintptr(i)))) } jsonStr := string(jsonData) globalLogger.Info("[辅助程序] 从客户端 %d 接收数据: %s", iClientId, jsonStr) // 尝试解析数据为JSON var responseData map[string]interface{} // 移除末尾的空字符 cleanJsonStr := strings.TrimRight(jsonStr, "\x00") if err := json.Unmarshal([]byte(cleanJsonStr), &responseData); err != nil { // 如果解析失败,仍然创建一个包含原始字符串的数据结构 responseData = map[string]interface{}{ "rawData": cleanJsonStr, } } recordDashboardMessage(iClientId, "incoming", cleanJsonStr, nil, "recv-callback") recordClientProbeEvent(iClientId, responseData, cleanJsonStr) if autoReplyEngine != nil { autoReplyEngine.observeGroupNames(iClientId, responseData) autoReplyEngine.observeIdentityContacts(iClientId, responseData) } if typeVal, ok := responseData["type"]; ok && (typeVal == float64(11024)) { if data, ok := responseData["data"].(map[string]interface{}); ok { pid := uint32(intFromAny(data["pid"])) registerClientPID(uint32(iClientId), pid) startClientIdentification(uint32(iClientId)) } return 0 } if typeVal, ok := responseData["type"]; ok && typeVal == float64(11041) { markClientMessageReady(uint32(iClientId), responseData) } if data, ok := responseData["data"].(map[string]interface{}); ok { if createTime, ok := data["create_time"].(float64); ok { // 将时间戳转换为时间 timeValue := time.Unix(int64(createTime), 0) // 获取今天和昨天的时间 today := time.Now().Truncate(24 * time.Hour) yesterday := today.Add(-24 * time.Hour) // 截断时间到天 timeValueDay := timeValue.Truncate(24 * time.Hour) // 判断是否是今天或昨天 if timeValueDay != today && timeValueDay != yesterday { // 不是今天或昨天,结束处理 return 0 } } // 处理send_time字段,支持字符串和数字类型 if sendTimeVal, ok := data["send_time"]; ok { var sendTime int64 var err error // 根据类型进行处理 switch v := sendTimeVal.(type) { case string: // 如果是字符串类型,尝试转换为int64 sendTime, err = strconv.ParseInt(v, 10, 64) case float64: // 如果是float64类型,直接转换为int64 sendTime = int64(v) } // 如果转换成功,执行日期判断 if err == nil { // 将时间戳转换为时间 timeValue := time.Unix(sendTime, 0) // 获取今天和昨天的时间 today := time.Now().Truncate(24 * time.Hour) yesterday := today.Add(-24 * time.Hour) // 截断时间到天 timeValueDay := timeValue.Truncate(24 * time.Hour) // 判断是否是今天或昨天 if timeValueDay != today && timeValueDay != yesterday { // 不是今天或昨天,结束处理 return 0 } } } } observeAfterSalesEvent(iClientId, responseData) // 检查type是否为11026,如果是则提取user_id并更新client映射 if typeVal, ok := responseData["type"]; ok && (typeVal == float64(11026) || typeVal == float64(11179)) { if userID, accountData := extractAccountIdentity(responseData); userID != "" { markClientIdentified(uint32(iClientId), userID, accountData) globalLogger.Info("[辅助程序] account event identified clientId=%d -> user_id=%s", iClientId, userID) } } if typeVal, ok := responseData["type"]; ok && (typeVal == float64(11026)) { if data, ok := responseData["data"].(map[string]interface{}); ok { if userID, ok := data["user_id"].(string); ok { // 更新客户端映射 markClientIdentified(uint32(iClientId), userID, data) globalLogger.Info("[辅助程序] 11026更新客户端映射: clientId=%d -> user_id=%s", iClientId, userID) } } } // 检查type是否为11179,如果是则提取user_id并更新client映射 if typeVal, ok := responseData["type"]; ok && (typeVal == float64(11179)) { if data, ok := responseData["data"].(map[string]interface{}); ok { if userID, ok := data["user_id"].(string); ok { // 更新客户端映射 markClientIdentified(uint32(iClientId), userID, data) globalLogger.Info("[辅助程序] 11179更新客户端映射: clientId=%d -> user_id=%s", iClientId, userID) } } } // 检查是否有对应的响应通道 // 记录当前ResponseMap中的所有clientId,用于调试 responseMapMu.Lock() globalLogger.Info("[辅助程序] 当前ResponseMap中的clientId列表: %v", reflect.ValueOf(ResponseMap).MapKeys()) responseMapMu.Unlock() if ch, exists := GetResponseChannel(iClientId); exists { // 使用TransformData函数转换数据 jsonBytes, err := TransformData(responseData, uint32(iClientId)) if err != nil { globalLogger.Error("[错误] 转换数据失败: %v", err) return 0 } // 将jsonBytes转换为responseData格式 var transformedData map[string]interface{} if err := json.Unmarshal(jsonBytes, &transformedData); err != nil { globalLogger.Error("[错误] 解析转换后的数据失败: %v", err) // 如果解析失败,仍然使用原始responseData transformedData = responseData } if autoReplyEngine != nil { autoReplyEngine.observeIdentityContacts(iClientId, transformedData) } // 发送数据到响应通道 select { case ch <- ClientResponseData{ ClientId: iClientId, Data: transformedData, }: globalLogger.Info("[辅助程序] 已将数据发送到响应通道, clientId: %d", iClientId) default: globalLogger.Warn("[辅助程序] 响应通道已满,无法发送数据, clientId: %d", iClientId) } } else { globalLogger.Debug("[错误] 未找到对应的响应通道, clientId: %d", iClientId) autoReplyData := responseData if jsonBytes, err := TransformData(responseData, uint32(iClientId)); err == nil { var transformedData map[string]interface{} if err := json.Unmarshal(jsonBytes, &transformedData); err == nil { autoReplyData = transformedData } else { globalLogger.Error("[错误] 解析自动客服转换数据失败: %v", err) } } else { globalLogger.Error("[错误] 自动客服转换数据失败: %v", err) } enqueueAutoReplyEvent(iClientId, autoReplyData) // 尝试列出所有可用的响应通道,帮助调试 responseMapMu.Lock() for id := range ResponseMap { globalLogger.Error("[错误] 可用的响应通道clientId: %d", id) } responseMapMu.Unlock() // 如果没有对应的响应通道,并且启用了回调功能,则将数据发送到配置的回调接口 appConfig := config.GetGlobalConfig() if appConfig != nil && appConfig.CallbackConfig.EnableCallback && appConfig.CallbackConfig.CallbackURL != "" { globalLogger.Info("[辅助程序] 没有找到对应的响应通道,将数据发送到配置的回调接口: %s", appConfig.CallbackConfig.CallbackURL) // 使用TransformData函数转换数据 jsonBytes, err := TransformData(responseData, uint32(iClientId)) if err != nil { globalLogger.Error("[错误] 转换数据失败: %v", err) } else { // 发送HTTP POST请求到回调URL resp, err := http.Post(appConfig.CallbackConfig.CallbackURL, "application/json", bytes.NewBuffer(jsonBytes)) if err != nil { globalLogger.Error("[错误] 发送数据到回调接口失败: %v", err) } else { defer resp.Body.Close() globalLogger.Info("[辅助程序] 数据已成功发送到回调接口,响应状态码: %d", resp.StatusCode) } } } else if appConfig != nil { globalLogger.Info("[辅助程序] 回调功能未启用或回调URL为空,不发送数据") } else { globalLogger.Info("[辅助程序] 全局配置不存在,不发送数据到回调接口") } } return 0 } // MyCloseCallback 关闭回调函数,在客户端退出时调用 // 函数原型: void __stdcall MyCloseCallback(int iClientId) // 注意:为了兼容syscall.NewCallback,需要返回一个uintptr类型的值 // //export MyCloseCallback func MyCloseCallback(iClientId int32) uintptr { globalLogger.Info("[辅助程序] 客户端 %d 已断开连接", iClientId) // 检查该客户端是否还存在响应通道 if ch, exists := GetResponseChannel(iClientId); exists { // 发送客户端退出的信息到响应通道 select { case ch <- ClientResponseData{ ClientId: iClientId, Data: map[string]interface{}{ "type": 99999, "message": "客户端已退出", "client_id": iClientId, "status": "disconnected", "timestamp": time.Now().Unix(), }, }: globalLogger.Info("[辅助程序] 已向响应通道发送客户端退出信息, clientId: %d", iClientId) default: globalLogger.Warn("[辅助程序] 响应通道已满,无法发送客户端退出信息, clientId: %d", iClientId) } // 移除响应通道 RemoveResponseChannel(iClientId) globalLogger.Info("[辅助程序] 已移除响应通道, clientId: %d", iClientId) } else { globalLogger.Debug("[辅助程序] 客户端 %d 不存在响应通道,无需发送退出信息", iClientId) } // 如果断开连接的是当前全局clientId,重置全局clientId removeClientState(uint32(iClientId)) return 0 } // InitWxWorkSocket 初始化企业微信Socket连接并设置回调函数 // 函数原型: BOOL __stdcall InitWxWorkSocket(IN DWORD dwConnectCallback, IN DWORD dwRecvCallback, IN DWORD dwCloseCallback) func InitWxWorkSocket() (bool, error) { // 添加异常捕获机制 doneChan := make(chan struct { success bool err error }, 1) go func() { defer func() { if r := recover(); r != nil { // 捕获到panic,记录错误信息 errorMsg := fmt.Sprintf("初始化企业微信Socket连接时发生panic: %v", r) globalLogger.Error("[严重错误] %s", errorMsg) // 记录到错误文件 exePath, _ := os.Executable() exeDir := filepath.Dir(exePath) errFile, _ := os.Create(filepath.Join(exeDir, "socket_init_crash.txt")) if errFile != nil { timestamp := time.Now().Format("2006-01-02 15:04:05.000") fmt.Fprintf(errFile, "[%s] %s\n", timestamp, errorMsg) errFile.Close() } doneChan <- struct { success bool err error }{false, fmt.Errorf("%s", errorMsg)} } }() loaderFuncsMutex.Lock() loaderFuncs := globalLoaderFuncs loaderFuncsMutex.Unlock() if loaderFuncs == nil || loaderFuncs.InitWxWorkSocket == 0 { globalLogger.Error("[错误] InitWxWorkSocket函数不可用") doneChan <- struct { success bool err error }{false, fmt.Errorf("InitWxWorkSocket函数不可用")} return } // 获取回调函数地址 connectCallback := syscall.NewCallback(MyConnectCallback) recvCallback := syscall.NewCallback(MyRecvCallback) closeCallback := syscall.NewCallback(MyCloseCallback) globalLogger.Info("[辅助程序] 调用InitWxWorkSocket函数") globalLogger.Info("[辅助程序] - 函数地址: 0x%x", loaderFuncs.InitWxWorkSocket) globalLogger.Info("[辅助程序] - 连接回调地址: 0x%x", connectCallback) globalLogger.Info("[辅助程序] - 接收回调地址: 0x%x", recvCallback) globalLogger.Info("[辅助程序] - 关闭回调地址: 0x%x", closeCallback) // 设置超时 timeout := time.After(5 * time.Second) resultChan := make(chan struct { success bool err error }, 1) go func() { // 调用InitWxWorkSocket函数 ret, _, callErr := syscall.Syscall( loaderFuncs.InitWxWorkSocket, 3, connectCallback, recvCallback, closeCallback, ) if ret != 0 { globalLogger.Info("[辅助程序] InitWxWorkSocket调用成功") if callErr != 0 { globalLogger.Warn("[辅助程序] InitWxWorkSocket返回成功,但LastError非空,可忽略: %v", callErr) } resultChan <- struct { success bool err error }{true, nil} } else { globalLogger.Error("[错误] 调用InitWxWorkSocket失败: 返回值=%d, 错误=%v", ret, callErr) resultChan <- struct { success bool err error }{false, fmt.Errorf("调用InitWxWorkSocket失败: 返回值=%d, 错误=%v", ret, callErr)} } }() // 等待结果或超时 select { case result := <-resultChan: doneChan <- result case <-timeout: globalLogger.Error("[严重错误] 调用InitWxWorkSocket超时") doneChan <- struct { success bool err error }{false, fmt.Errorf("调用InitWxWorkSocket超时")} } }() // 等待goroutine执行完成 result := <-doneChan return result.success, result.err } // updateClientStatusWithResponseData 根据响应数据补充client_status.json中缺少的字段 func updateClientStatusWithResponseData(userID string, responseData map[string]interface{}) { // 获取程序路径 exePath, err := os.Executable() if err != nil { globalLogger.Error("[辅助程序] 获取程序路径失败: %v", err) return } exeDir := filepath.Dir(exePath) clientStatusFile := filepath.Join(exeDir, "config", "client_status.json") // 读取现有的client_status.json文件 var clientStatus map[string]interface{} if data, err := os.ReadFile(clientStatusFile); err == nil { if err := json.Unmarshal(data, &clientStatus); err != nil { globalLogger.Error("[辅助程序] 读取client_status文件失败: %v", err) return } } else { // 如果文件不存在,创建一个新的map clientStatus = make(map[string]interface{}) } // 检查用户数据是否存在 userData, exists := clientStatus[userID] if !exists { // 如果用户不存在,创建新的用户数据 userData = make(map[string]interface{}) clientStatus[userID] = userData } // 将userData转换为map[string]interface{}类型 userMap, ok := userData.(map[string]interface{}) if !ok { if globalLogger != nil { globalLogger.Error("[辅助程序] client_status.json中用户数据格式错误") } return } // 定义需要补充的字段 fieldsToUpdate := []string{ "account", "acctid", "avatar", "corp_id", "corp_name", "corp_short_name", "email", "job_name", "mobile", "nickname", "position", "sex", "username", } // 补充缺少的字段 updated := false for _, field := range fieldsToUpdate { if _, exists := userMap[field]; !exists { if value, ok := responseData[field]; ok { userMap[field] = value updated = true if globalLogger != nil { globalLogger.Info("[辅助程序] 补充用户 %s 的字段 %s: %v", userID, field, value) } } } } // 确保status字段存在且为1 if _, exists := userMap["status"]; !exists || userMap["status"] != 1 { userMap["status"] = 1 updated = true if globalLogger != nil { globalLogger.Info("[辅助程序] 补充用户 %s 的status字段: 1", userID) } } // 确保user_id字段存在 if _, exists := userMap["user_id"]; !exists { userMap["user_id"] = userID updated = true if globalLogger != nil { globalLogger.Info("[辅助程序] 补充用户 %s 的user_id字段", userID) } } // 如果有更新,写入文件 if updated { clientStatusJSON, err := json.MarshalIndent(clientStatus, "", " ") if err != nil { if globalLogger != nil { globalLogger.Error("[辅助程序] 序列化client_status数据失败: %v", err) } return } if err := os.WriteFile(clientStatusFile, clientStatusJSON, 0644); err != nil { if globalLogger != nil { globalLogger.Error("[辅助程序] 写入client_status文件失败: %v", err) } return } if globalLogger != nil { globalLogger.Info("[辅助程序] 已更新client_status.json文件,补充了用户 %s 的字段", userID) } } } // processType11171Data 处理type=11171的数据,将URL中的斜杠转换为转义斜杠 func processType11171Data(jsonData string) (string, error) { var rData map[string]interface{} if err := json.Unmarshal([]byte(jsonData), &rData); err != nil { return jsonData, fmt.Errorf("解析JSON数据失败: %v", err) } // 检查是否有data字段 if dataField, ok := rData["data"]; ok { if dataMap, ok := dataField.(map[string]interface{}); ok { // 检查是否有url字段 if urlValue, ok := dataMap["url"]; ok { if urlStr, ok := urlValue.(string); ok { // 将URL中的斜杠转换为JSON兼容的转义斜杠 processedURL := strings.ReplaceAll(urlStr, "/", "\\/") dataMap["url"] = processedURL //globalLogger.Info("[辅助程序] 处理type=11171的URL: %s -> %s", urlStr, processedURL) } } } } // 将处理后的数据转换回JSON字符串 /* processedJSON, err := json.Marshal(rData) if err != nil { return jsonData, fmt.Errorf("转换处理后的数据失败: %v", err) } */ var buf bytes.Buffer encoder := json.NewEncoder(&buf) encoder.SetEscapeHTML(false) err := encoder.Encode(rData) if err != nil { return jsonData, fmt.Errorf("转换处理后的数据失败: %v", err) } processedJSON := buf.Bytes() result := strings.ReplaceAll(string(processedJSON), "\\\\/", "\\/") return string(result), nil } // processNetworkResources 处理JSON数据中的网络资源(图片、视频、表情等) // 下载网络资源到exe同级目录的temp文件夹,并替换为本地路径 func processNetworkResources(jsonData string) (string, error) { // 获取程序路径 exePath, err := os.Executable() if err != nil { return jsonData, fmt.Errorf("获取程序路径失败: %v", err) } exeDir := filepath.Dir(exePath) parentDir := filepath.Dir(exeDir) tempDir := filepath.Join(parentDir, "temp") // 创建temp目录(如果不存在) if err := os.MkdirAll(tempDir, 0755); err != nil { return jsonData, fmt.Errorf("创建temp目录失败: %v", err) } // 清理temp目录中的旧文件(保留最近7天的文件) cleanOldFiles(tempDir, 3*24*time.Hour) // 解析JSON数据 var data interface{} if err := json.Unmarshal([]byte(jsonData), &data); err != nil { return jsonData, fmt.Errorf("解析JSON数据失败: %v", err) } // 递归处理数据结构中的网络资源 processedData := processDataRecursive(data, tempDir) // 将处理后的数据转换回JSON字符串 processedJSON, err := json.Marshal(processedData) if err != nil { return jsonData, fmt.Errorf("转换处理后的数据失败: %v", err) } return string(processedJSON), nil } // processDataRecursive 递归处理数据结构中的网络资源 func processDataRecursive(data interface{}, tempDir string) interface{} { switch v := data.(type) { case map[string]interface{}: // 处理map类型 result := make(map[string]interface{}) for key, value := range v { // 检查是否为网络资源字段 if isNetworkResourceField(key, value) { if strValue, ok := value.(string); ok { if isNetworkURL(strValue) { // 下载网络资源并获取本地路径 localPath, err := downloadNetworkResource(strValue, tempDir) if err == nil { // 将本地路径赋值给相应的file字段 fileKey := key result[fileKey] = localPath globalLogger.Info("[网络资源处理] 已下载网络资源: %s -> %s", strValue, localPath) } else { globalLogger.Warn("[网络资源处理] 下载网络资源失败: %s, 错误: %v", strValue, err) result[key] = value } } else { result[key] = value } } else { result[key] = processDataRecursive(value, tempDir) } } else { result[key] = processDataRecursive(value, tempDir) } } return result case []interface{}: // 处理数组类型 result := make([]interface{}, len(v)) for i, item := range v { result[i] = processDataRecursive(item, tempDir) } return result default: // 其他类型直接返回 return data } } // isNetworkResourceField 判断是否为网络资源字段 func isNetworkResourceField(key string, value interface{}) bool { // 常见的网络资源字段名 resourceFields := []string{"image", "img", "picture", "pic", "photo", "video", "media", "file", "url", "link", "src", "source", "path", "content"} keyLower := strings.ToLower(key) for _, field := range resourceFields { if strings.Contains(keyLower, field) { return true } } return false } // isNetworkURL 判断字符串是否为网络URL func isNetworkURL(str string) bool { // 检查是否为HTTP/HTTPS URL return strings.HasPrefix(strings.ToLower(str), "http://") || strings.HasPrefix(strings.ToLower(str), "https://") } // downloadNetworkResource 下载网络资源到本地 func downloadNetworkResource(url, tempDir string) (string, error) { // 创建HTTP客户端 client := &http.Client{ Timeout: 30 * time.Second, } // 发送GET请求 resp, err := client.Get(url) if err != nil { return "", fmt.Errorf("请求网络资源失败: %v", err) } defer resp.Body.Close() // 检查响应状态 if resp.StatusCode != http.StatusOK { return "", fmt.Errorf("下载失败,状态码: %d", resp.StatusCode) } // 从URL中提取文件名 fileName := extractFileNameFromURL(url) if fileName == "" { // 如果无法从URL提取文件名,使用当前时间戳 fileName = fmt.Sprintf("resource_%d", time.Now().Unix()) } // 生成本地文件路径 localPath := filepath.Join(tempDir, fileName) // 使用安全文件创建方法处理文件占用问题 file, actualPath, err := SafeCreateFile(localPath) if err != nil { return "", fmt.Errorf("创建本地文件失败: %v", err) } defer file.Close() // 将响应内容写入文件 _, err = io.Copy(file, resp.Body) if err != nil { // 如果写入失败,删除已创建的文件 os.Remove(actualPath) return "", fmt.Errorf("写入文件失败: %v", err) } return actualPath, nil } // extractFileNameFromURL 从URL中提取文件名 func extractFileNameFromURL(urlStr string) string { // 解析URL parsedURL, err := url.Parse(urlStr) if err != nil { return "" } // 从路径中提取文件名 path := parsedURL.Path if path == "" || path == "/" { return "" } // 获取最后一个路径段 segments := strings.Split(path, "/") fileName := segments[len(segments)-1] // 清理文件名中的特殊字符 fileName = sanitizeFileNameWithLimit(fileName) // 如果文件名没有扩展名,尝试从Content-Type推断 if filepath.Ext(fileName) == "" { ext := inferExtensionFromURL(urlStr) if ext != "" { fileName += ext } } return fileName } // sanitizeFileNameWithLimit 清理文件名中的特殊字符并限制长度 func sanitizeFileNameWithLimit(fileName string) string { // 移除或替换不合法的文件名字符 invalidChars := []string{"<", ">", ":", "\"", "|", "?", "*", "\\", "/"} result := fileName for _, char := range invalidChars { result = strings.ReplaceAll(result, char, "_") } // 限制文件名长度 if len(result) > 100 { result = result[:100] } return result } // inferExtensionFromURL 根据URL推断文件扩展名 func inferExtensionFromURL(url string) string { // 根据URL特征推断文件类型 urlLower := strings.ToLower(url) // 图片类型 if strings.Contains(urlLower, ".jpg") || strings.Contains(urlLower, ".jpeg") { return ".jpg" } if strings.Contains(urlLower, ".png") { return ".png" } if strings.Contains(urlLower, ".gif") { return ".gif" } if strings.Contains(urlLower, ".bmp") { return ".bmp" } if strings.Contains(urlLower, ".webp") { return ".webp" } if strings.Contains(urlLower, ".svg") { return ".svg" } if strings.Contains(urlLower, ".ico") { return ".ico" } if strings.Contains(urlLower, ".tiff") || strings.Contains(urlLower, ".tif") { return ".tiff" } if strings.Contains(urlLower, ".psd") { return ".psd" } if strings.Contains(urlLower, ".raw") { return ".raw" } if strings.Contains(urlLower, ".heic") { return ".heic" } if strings.Contains(urlLower, ".avif") { return ".avif" } // 视频类型 if strings.Contains(urlLower, ".mp4") { return ".mp4" } if strings.Contains(urlLower, ".avi") { return ".avi" } if strings.Contains(urlLower, ".mov") { return ".mov" } if strings.Contains(urlLower, ".wmv") { return ".wmv" } if strings.Contains(urlLower, ".flv") { return ".flv" } if strings.Contains(urlLower, ".webm") { return ".webm" } if strings.Contains(urlLower, ".mkv") { return ".mkv" } if strings.Contains(urlLower, ".m4v") { return ".m4v" } if strings.Contains(urlLower, ".3gp") { return ".3gp" } if strings.Contains(urlLower, ".mpg") || strings.Contains(urlLower, ".mpeg") { return ".mpg" } if strings.Contains(urlLower, ".m2v") { return ".m2v" } if strings.Contains(urlLower, ".mts") { return ".mts" } if strings.Contains(urlLower, ".ogv") { return ".ogv" } if strings.Contains(urlLower, ".ts") { return ".ts" } if strings.Contains(urlLower, ".vob") { return ".vob" } // 音频类型 if strings.Contains(urlLower, ".mp3") { return ".mp3" } if strings.Contains(urlLower, ".wav") { return ".wav" } if strings.Contains(urlLower, ".flac") { return ".flac" } if strings.Contains(urlLower, ".aac") { return ".aac" } if strings.Contains(urlLower, ".ogg") { return ".ogg" } if strings.Contains(urlLower, ".m4a") { return ".m4a" } if strings.Contains(urlLower, ".wma") { return ".wma" } if strings.Contains(urlLower, ".opus") { return ".opus" } if strings.Contains(urlLower, ".amr") { return ".amr" } if strings.Contains(urlLower, ".mid") || strings.Contains(urlLower, ".midi") { return ".mid" } if strings.Contains(urlLower, ".ape") { return ".ape" } if strings.Contains(urlLower, ".aiff") || strings.Contains(urlLower, ".aif") { return ".aiff" } // 文档类型 if strings.Contains(urlLower, ".pdf") { return ".pdf" } if strings.Contains(urlLower, ".doc") { return ".doc" } if strings.Contains(urlLower, ".docx") { return ".docx" } if strings.Contains(urlLower, ".xls") { return ".xls" } if strings.Contains(urlLower, ".xlsx") { return ".xlsx" } if strings.Contains(urlLower, ".ppt") { return ".ppt" } if strings.Contains(urlLower, ".pptx") { return ".pptx" } if strings.Contains(urlLower, ".txt") { return ".txt" } if strings.Contains(urlLower, ".csv") { return ".csv" } if strings.Contains(urlLower, ".rtf") { return ".rtf" } if strings.Contains(urlLower, ".odt") { return ".odt" } if strings.Contains(urlLower, ".ods") { return ".ods" } if strings.Contains(urlLower, ".odp") { return ".odp" } // 压缩文件 if strings.Contains(urlLower, ".zip") { return ".zip" } if strings.Contains(urlLower, ".rar") { return ".rar" } if strings.Contains(urlLower, ".7z") { return ".7z" } if strings.Contains(urlLower, ".tar") { return ".tar" } if strings.Contains(urlLower, ".gz") { return ".gz" } if strings.Contains(urlLower, ".bz2") { return ".bz2" } // 可执行文件 if strings.Contains(urlLower, ".exe") { return ".exe" } if strings.Contains(urlLower, ".msi") { return ".msi" } if strings.Contains(urlLower, ".deb") { return ".deb" } if strings.Contains(urlLower, ".rpm") { return ".rpm" } if strings.Contains(urlLower, ".dmg") { return ".dmg" } // 网页相关 if strings.Contains(urlLower, ".html") || strings.Contains(urlLower, ".htm") { return ".html" } if strings.Contains(urlLower, ".css") { return ".css" } if strings.Contains(urlLower, ".js") { return ".js" } if strings.Contains(urlLower, ".json") { return ".json" } if strings.Contains(urlLower, ".xml") { return ".xml" } // 字体文件 if strings.Contains(urlLower, ".ttf") { return ".ttf" } if strings.Contains(urlLower, ".otf") { return ".otf" } if strings.Contains(urlLower, ".woff") { return ".woff" } if strings.Contains(urlLower, ".woff2") { return ".woff2" } // 数据库文件 if strings.Contains(urlLower, ".sql") { return ".sql" } if strings.Contains(urlLower, ".db") { return ".db" } if strings.Contains(urlLower, ".sqlite") { return ".sqlite" } return "" } // cleanOldFiles 清理temp目录中的旧文件 func cleanOldFiles(tempDir string, maxAge time.Duration) { entries, err := os.ReadDir(tempDir) if err != nil { globalLogger.Warn("[网络资源处理] 读取temp目录失败: %v", err) return } cutoffTime := time.Now().Add(-maxAge) for _, entry := range entries { if entry.IsDir() { continue } info, err := entry.Info() if err != nil { continue } // 删除超过最大年龄的文件 if info.ModTime().Before(cutoffTime) { filePath := filepath.Join(tempDir, entry.Name()) if err := os.Remove(filePath); err == nil { globalLogger.Debug("[网络资源处理] 删除旧文件: %s", filePath) } } } }