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