Files
qiweimanager-master/helper/helper.go

1947 lines
60 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
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)
}
}
}
}